Skip to content

Commit 012c4e4

Browse files
committed
smb: refuse OPEN_ANDX on directories
handleOpenAndX handed out a FID over os.OpenFile(dir) without checking IsDir first, unlike OPEN/CREATE. The open itself succeeded (attrs correctly reported Directory), so a directory-copy client only found out on the follow-up READ, which failed with the generic statusUnsuccessful (ERRSRV/ERRerror) — a code CORE-dialect clients can't act on, so the whole copy aborted (seen client-side as Windows "System error 1026" against an IPX SMB share). Reject with STATUS_FILE_IS_A_DIRECTORY at open time instead, matching handleOpen/handleCreate/handleNTCreateAndX.
1 parent 2c5a2f6 commit 012c4e4

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

core/service/smb/fileio.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ func (s *Service) handleOpenAndX(sess *smbSession, h protocol.Header, req []byte
6363
if st != statusSuccess {
6464
return errResponse(h, st)
6565
}
66+
if info, err := sh.FS().Stat(store); err == nil && info.IsDir() {
67+
// Unlike OPEN/CREATE, this path handed out a FID over
68+
// os.OpenFile(dir) without checking IsDir first — the open succeeded
69+
// (attrs correctly reported Directory), but the follow-up
70+
// READ/READ_MPX on that FID then failed with the generic
71+
// statusUnsuccessful (ERRSRV/ERRerror), a code CORE-dialect clients
72+
// can't act on. A directory-copy walks FIND_FIRST2 results and opens
73+
// each entry by name, depending on OPEN_ANDX itself rejecting
74+
// directories to know to recurse instead of stream-reading it: an
75+
// IPX SMB capture of a stalled directory copy showed the Open AndX
76+
// Response reporting File Attributes Directory, then two Read
77+
// attempts both coming back ERRSRV/ERRerror before the client's copy
78+
// aborted (Windows-side "System error 1026").
79+
return errResponse(h, statusFileIsADirectory)
80+
}
6681

6782
// OpenFunction (low nibble = action if exists, high nibble = action if
6883
// missing): 0x0001 open, 0x0002 truncate, 0x0010 create-if-missing. The

core/service/smb/fileio_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,31 @@ func TestFS_OpenMissingFileNotFound(t *testing.T) {
313313
t.Fatalf("OPEN_ANDX missing status = %#x, want OBJECT_NAME_NOT_FOUND", h.Status)
314314
}
315315
}
316+
317+
// TestFS_OpenAndXOnDirectoryRefused proves OPEN_ANDX (0x2D) on a directory is
318+
// refused with STATUS_FILE_IS_A_DIRECTORY, matching OPEN/CREATE. Before this,
319+
// OPEN_ANDX had no IsDir check: it handed out a FID over os.OpenFile(dir), which
320+
// succeeded (attrs correctly reported Directory), and only the follow-up READ
321+
// failed — with the generic ERRSRV/ERRerror a CORE-dialect directory-copy client
322+
// can't distinguish from an ordinary I/O fault, so it aborted the whole copy
323+
// instead of recursing (observed as Windows "System error 1026" over an IPX SMB
324+
// capture).
325+
func TestFS_OpenAndXOnDirectoryRefused(t *testing.T) {
326+
svc, sess, tid := fsService(t)
327+
328+
mkdirReq := smbReq(protocol.CommandCreateDirectory, protocol.Flags2NTStatus, tid, 1, nil, ansiPathArea("subdir"))
329+
if h := respHeader(t, svc.Dispatch(sess, mkdirReq)); h.Status != statusSuccess {
330+
t.Fatalf("CREATE_DIRECTORY status = %#x", h.Status)
331+
}
332+
333+
ow := make([]byte, 30)
334+
ow[0] = protocol.CommandNoAndXCommand
335+
bp.PutLE16(ow[16:18], 0x01) // open existing, no create bit
336+
reply := svc.Dispatch(sess, smbReq(protocol.CommandOpenAndX, protocol.Flags2NTStatus, tid, 1, ow, ansiPathArea("subdir")))
337+
if h := respHeader(t, reply); h.Status != statusFileIsADirectory {
338+
t.Fatalf("OPEN_ANDX on directory status = %#x, want STATUS_FILE_IS_A_DIRECTORY", h.Status)
339+
}
340+
if len(sess.fids) != 0 {
341+
t.Fatalf("OPEN_ANDX on directory left %d FID(s) open, want 0", len(sess.fids))
342+
}
343+
}

0 commit comments

Comments
 (0)