diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h index 653cb3579d40..6826f7bed1a4 100644 --- a/fs/smb/common/smb2pdu.h +++ b/fs/smb/common/smb2pdu.h @@ -1261,6 +1261,14 @@ struct create_mxac_req { #define SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE 2 #define SMB2_CRTCTX_AAPL_UNIX_BASED 4 #define SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE 8 +/* + * V2 extends the same inline-FinderInfo mechanism as + * SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR with an added flags field, + * confirmed byte-identical to V1 otherwise against AAPL's actual + * public client behavior. Mutually exclusive with the V1 bit on + * the wire, not both set together. + */ +#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2 16 /* "AAPL" Volume Capabilities bitmap */ #define SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID 1 diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 05818a165e22..ddfcddd3c09c 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -125,6 +125,7 @@ struct ksmbd_conn { atomic_t refcnt; bool is_aapl; bool aapl_readdir_attr; /* READDIR_ATTR negotiated */ + bool aapl_readdir_attr_v2; /* V2 specifically */ struct work_struct release_work; }; diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 94fe464a85f3..0fe82e740ffb 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -2158,11 +2158,15 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp) * * Sending reply_bitmap with MODEL_INFO set but no model string causes * smbfs.kext to enter a broken disconnect path requiring a macOS reboot. + * @readdir_attr_v2: advertise SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2 + * instead of the V1 bit */ -void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap) +void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap, + bool readdir_attr_v2) { struct create_aapl_rsp *buf; u64 reply_bitmap; + u64 server_caps; u32 data_len; buf = (struct create_aapl_rsp *)cc; @@ -2188,8 +2192,12 @@ void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap) buf->cmd = cpu_to_le32(SMB2_CRTCTX_AAPL_SERVER_QUERY); buf->reply_bitmap = cpu_to_le64(reply_bitmap); + server_caps = AAPL_SERVER_CAPS_KSMBD; + if (readdir_attr_v2) + server_caps = (server_caps & ~SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) | + SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2; buf->server_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) ? - cpu_to_le64(AAPL_SERVER_CAPS_KSMBD) : 0; + cpu_to_le64(server_caps) : 0; buf->vol_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) ? cpu_to_le64(vol_caps) : 0; diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h index f7f6afcc5434..aef296b21395 100644 --- a/fs/smb/server/oplock.h +++ b/fs/smb/server/oplock.h @@ -125,7 +125,8 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp); void create_mxac_rsp_buf(char *cc, int maximal_access); void create_disk_id_rsp_buf(char *cc, __u64 file_id, __u64 vol_id); void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp); -void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap); +void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap, + bool readdir_attr_v2); struct create_context *smb2_find_context_vals(void *open_req, const char *tag, int tag_len); struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn, char *lease_key); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 65d367de43cd..e3be1da76dc6 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -4413,12 +4413,23 @@ int smb2_open(struct ksmbd_work *work) if (aapl_ctxt) { if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) conn->aapl_readdir_attr = true; + /* + * V2 extends the same inline-FinderInfo mechanism (see + * smb2pdu.h), so a V2-requesting client also gets + * aapl_readdir_attr treatment -- the reply just advertises + * the V2 bit instead of the V1 one (create_aapl_rsp_buf). + */ + if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2) { + conn->aapl_readdir_attr = true; + conn->aapl_readdir_attr_v2 = true; + } contxt_cnt++; create_aapl_rsp_buf(rsp->Buffer + le32_to_cpu(rsp->CreateContextsLength), SMB2_CRTCTX_AAPL_FULL_SYNC, - aapl_req_bitmap); + aapl_req_bitmap, + conn->aapl_readdir_attr_v2); le32_add_cpu(&rsp->CreateContextsLength, conn->vals->create_aapl_size); iov_len += conn->vals->create_aapl_size; @@ -4762,6 +4773,11 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level, * the file extension for icon lookup) * Reserved2 = Unix mode bits (uint16 LE) * Reparse-point tag is indicated via ExtFileAttributes, not EaSize. + * + * V2 (conn->aapl_readdir_attr_v2): ShortNameLength+Reserved + * are read as a single flags field instead of being ignored + * -- see smb2pdu.h for the wire-format confirmation and + * AAPL_READDIR_ATTR_V2_NO_XATTR's meaning. */ __le32 reparse_tag = smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode); @@ -4791,7 +4807,20 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level, * interpreted; V1 doesn't. Either value is safe * here, so keep 24 for parity. */ - fibdinfo->ShortNameLength = 24; + if (conn->aapl_readdir_attr_v2) { + /* + * V2 repurposes this field as flags (see comment + * above) -- 24 is a V1-only convention that real + * macOS clients ignore outright, so don't reuse it + * here as a base value for a field V2 clients + * actually interpret. + */ + fibdinfo->ShortNameLength = 0; + if (!ksmbd_kstat->has_ads_stream) + fibdinfo->ShortNameLength = AAPL_READDIR_ATTR_V2_NO_XATTR; + } else { + fibdinfo->ShortNameLength = 24; + } memset(fibdinfo->ShortName, 0, sizeof(fibdinfo->ShortName)); fibdinfo->Reserved2 = cpu_to_le16(ksmbd_kstat->kstat->mode & 0xffff); } else { diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index b0daca928c44..9d77400a1670 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -95,6 +95,21 @@ struct preauth_integrity_info { SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE | \ SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) +/* + * READDIR_ATTR_V2 (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2, see + * fs/smb/common/smb2pdu.h) extends the same inline-FinderInfo mechanism + * above with a flags field, confirmed byte-identical to V1 otherwise + * against AAPL's actual public client behavior. When a client's own + * client_caps requests V2, the server advertises V2 instead of V1 in + * its own server_caps reply; V1 and V2 are mutually exclusive on the + * wire, not both set together. The wire format's ShortNameLength+Reserved + * (ignored in V1) become a single flags field in V2 -- + * AAPL_READDIR_ATTR_V2_NO_XATTR is the only flag bit currently defined, + * signaling the item has no xattrs/streams so the client can skip a + * separate query. + */ +#define AAPL_READDIR_ATTR_V2_NO_XATTR 0x01 + /* Model string: up to 31 ASCII chars */ #define AAPL_MODEL_MAX_CHARS 31 #define AAPL_MODEL_UTF16_BYTES (AAPL_MODEL_MAX_CHARS * 2) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 14a895685970..0ec052e4a365 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -1684,6 +1684,35 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work, } } + /* + * Only pay for this when it'll actually be used: AAPL + * READDIR_ATTR_V2's flags field (AAPL_READDIR_ATTR_V2_NO_XATTR) is + * the only consumer. XATTR_NAME_STREAM ("user.DosStream.") is a + * reliable, distinct prefix for genuine ADS/stream xattrs -- unlike + * DOSATTRIB or ACL xattrs, which live under different prefixes, so + * this can't false-positive into telling Finder a file has no extra + * data when it actually does. + */ + ksmbd_kstat->has_ads_stream = false; + if (work->conn->aapl_readdir_attr_v2) { + char *xattr_list = NULL, *name; + ssize_t xattr_list_len; + + xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list); + if (xattr_list_len > 0) { + for (name = xattr_list; + name - xattr_list < xattr_list_len; + name += strlen(name) + 1) { + if (!strncmp(name, XATTR_NAME_STREAM, + XATTR_NAME_STREAM_LEN)) { + ksmbd_kstat->has_ads_stream = true; + break; + } + } + } + kvfree(xattr_list); + } + return 0; } diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 8eab9392ff89..2e8f9f2d95b0 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -70,6 +70,7 @@ struct ksmbd_kstat { struct kstat *kstat; unsigned long long create_time; __le32 file_attributes; + bool has_ads_stream; /* AAPL READDIR_ATTR V2 xattr-presence flag */ }; int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child);