[PATCH v2 6/6] ksmbd: add AAPL READDIR_ATTR V2 support
"Gaël Blivet-Bailly" <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
From: Gael Blivet <[email protected]> Extends the existing V1 inline-FinderInfo mechanism (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) with the V2 variant: byte-identical layout otherwise, confirmed against Apple's actual SMBClient kernel source (apple-oss-distributions/SMBClient/kernel/netsmb/smb_smb_2.c), except the ShortNameLength+Reserved bytes (ignored outright by V1 clients) become a single flags field that V2 clients actually interpret. Negotiation: when a client's own client_caps requests V2 (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2), advertise V2 instead of V1 in the server's own server_caps reply -- they're mutually exclusive on the wire, not both set together. Wire format: the only currently-defined V2 flag, AAPL_READDIR_ATTR_V2_NO_XATTR, signals that an item has no xattrs/streams so the client can skip a separate query. Compute this per-entry in ksmbd_vfs_fill_dentry_attrs() by checking for any xattr under the XATTR_NAME_STREAM ("user.DosStream.") prefix -- a reliable, distinct marker 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. Only computed when a V2 connection is active, to avoid the extra listxattr() call otherwise. V1's fixed ShortNameLength=24 convention (real macOS clients ignore the value outright per the same client source, so it's cosmetic parity with other real servers, not a functional requirement) is kept V1-only rather than reused as a V2 base value -- V2 clients do interpret this field, so it needs a clean 0-or-flag value, not a leftover V1 constant that happens not to collide with the one defined flag bit today. Confirmed via live diagnostics that macOS actually negotiates and uses V2 (client_caps bit 0x10 set) rather than falling back to V1 or ignoring the capability. Signed-off-by: Gael Blivet <[email protected]> --- New for v2. fs/smb/common/smb2pdu.h | 8 ++++++++ fs/smb/server/connection.h | 1 + fs/smb/server/oplock.c | 12 ++++++++++-- fs/smb/server/oplock.h | 3 ++- fs/smb/server/smb2pdu.c | 33 +++++++++++++++++++++++++++++++-- fs/smb/server/smb2pdu.h | 16 ++++++++++++++++ fs/smb/server/vfs.c | 29 +++++++++++++++++++++++++++++ fs/smb/server/vfs.h | 1 + 8 files changed, 98 insertions(+), 5 deletions(-) diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h index 653cb3579..cc1edd833 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 Apple's actual + * SMBClient kernel source (apple-oss-distributions/SMBClient). 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 fe64dd24b..3ecfe4a7e 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 693db52cd..f12f4e052 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -2156,11 +2156,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; @@ -2186,8 +2190,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 f7f6afcc5..aef296b21 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 612f8c8ec..bfaa9909e 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -4404,12 +4404,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; @@ -4753,6 +4764,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); @@ -4784,7 +4800,20 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level, * it as a flags field that is 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 740358a2b..737ee488f 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -96,6 +96,22 @@ 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 Apple's actual SMBClient kernel source + * (apple-oss-distributions/SMBClient). When the 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 61d02322c..d00438d43 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 8eab9392f..2e8f9f2d9 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); -- 2.43.0