ksmbd: add AAPL kAAPL_SERVER_QUERY create context support

macOS clients (Finder, and specifically Time Machine's backupd) send
an "AAPL" SMB2 create context on CREATE to negotiate AAPL-specific
server capabilities (server_caps/vol_caps/model string). Without a
response to this context, macOS Time Machine over SMB does not work
at all.

Add the AAPL create context structs (create_aapl_rsp,
aapl_server_query_req) and create_aapl_rsp_buf(), which builds the
kAAPL_SERVER_QUERY response mirroring the layout observed from macOS's
own smbd, including the model string workaround: omitting the model
string when the client requested it causes smbfs.kext to enter a
broken disconnect path requiring a full macOS reboot to recover from.
Command codes and bitmap values reuse the existing SMB2_CRTCTX_AAPL_*
constants in fs/smb/common/smb2pdu.h.

Wire format confirmed against AAPL's published public client kernel
source (public client behavior reference) -- every field
here and every SMB2_CRTCTX_AAPL_* constant matches exactly.

Hook the request parsing and response into smb2_open()'s existing
create-context handling, following the same DataOffset+DataLength
bounds-checking convention already used by every other context parser
in this file. The AAPL model string is configurable via the existing
netlink startup path (server_conf.aapl_model, default "Xserve").

This is scoped to shares with the new KSMBD_SHARE_FLAG_TIME_MACHINE
flag only, not enabled globally -- AAPL's AAPL extension is
undocumented, so containing its blast radius to shares that explicitly
opt in limits risk to ordinary SMB shares.

conn->aapl_readdir_attr is set here when the client also advertises
READDIR_ATTR support, but the actual inline-FinderInfo wire format
(the feature that flag gates) is not implemented yet -- follow-up
commit.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Gael Blivet 2026-07-09 14:59:56 +09:00 committed by Namjae Jeon
parent 1c3ebf832d
commit 8f1b796ff1
10 changed files with 212 additions and 2 deletions

View File

@ -39,6 +39,7 @@ struct smb_version_values {
size_t create_mxac_size;
size_t create_disk_id_size;
size_t create_posix_size;
size_t create_aapl_size;
};
static inline unsigned int get_rfc1002_len(void *buf)

View File

@ -124,6 +124,7 @@ struct ksmbd_conn {
bool binding;
atomic_t refcnt;
bool is_aapl;
bool aapl_readdir_attr; /* READDIR_ATTR negotiated */
struct work_struct release_work;
};

View File

@ -113,7 +113,8 @@ struct ksmbd_startup_request {
__u32 max_connections; /* Number of maximum simultaneous connections */
__s8 bind_interfaces_only;
__u32 max_ip_connections; /* Number of maximum connection per ip address */
__s8 reserved[499]; /* Reserved room */
__s8 aapl_model[32]; /* AAPL model string for Finder icon, e.g. "Xserve" */
__s8 reserved[467]; /* Reserved room */
__u32 ifc_list_sz; /* interfaces list size */
__s8 ____payload[];
} __packed;
@ -378,6 +379,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15)
#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16)
#define KSMBD_SHARE_FLAG_HIDE_UNREADABLE BIT(17)
#define KSMBD_SHARE_FLAG_TIME_MACHINE BIT(18)
/*
* Tree connect request flags.

View File

@ -16,6 +16,7 @@
#include "mgmt/user_session.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
#include "server.h"
static LIST_HEAD(lease_table_list);
static DEFINE_RWLOCK(lease_list_lock);
@ -2138,6 +2139,82 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp)
SIDUNIX_GROUP, (struct smb_sid *)&buf->SidBuffer[28]);
}
/**
* create_aapl_rsp_buf() - build AAPL kAAPL_SERVER_QUERY response
* @cc: buffer to write the create context into (AAPL_RSP_MAX_SIZE bytes)
* @vol_caps: volume capability flags (SMB2_CRTCTX_AAPL_* volume bits)
* @req_bitmap: the client's request bitmap, echoed back in reply_bitmap
*
* Response format follows the layout observed from macOS's own smbd, and
* matches the client-side parsing in AAPL's published public client kernel
* source (public client behavior reference, kAAPL_SERVER_QUERY
* case): reply_bitmap, then server_caps/vol_caps/model-info fields present
* only when their reply_bitmap bit is set:
* reply_bitmap = req_bitmap masked to the fields we support
* server_caps = AAPL_SERVER_CAPS_KSMBD when requested
* vol_caps = caller-supplied
* model string = server_conf.aapl_model (default "Xserve") in UTF-16LE,
* when SMB2_CRTCTX_AAPL_MODEL_INFO requested
*
* Sending reply_bitmap with MODEL_INFO set but no model string causes
* smbfs.kext to enter a broken disconnect path requiring a macOS reboot.
*/
void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap)
{
struct create_aapl_rsp *buf;
u64 reply_bitmap;
u32 data_len;
buf = (struct create_aapl_rsp *)cc;
memset(buf, 0, AAPL_RSP_MAX_SIZE);
reply_bitmap = req_bitmap & (SMB2_CRTCTX_AAPL_SERVER_CAPS |
SMB2_CRTCTX_AAPL_VOLUME_CAPS |
SMB2_CRTCTX_AAPL_MODEL_INFO);
/* base data: cmd(4)+reserved(4)+reply_bitmap(8)+server_caps(8)+vol_caps(8) */
data_len = 32;
if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO)
data_len += 4 + 4 + AAPL_MODEL_UTF16_BYTES; /* pad2+model_bytes+string */
buf->ccontext.DataOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, cmd));
buf->ccontext.DataLength = cpu_to_le32(data_len);
buf->ccontext.NameOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, Name));
buf->ccontext.NameLength = cpu_to_le16(SMB2_CREATE_AAPL_LEN);
buf->Name[0] = 'A';
buf->Name[1] = 'A';
buf->Name[2] = 'P';
buf->Name[3] = 'L';
buf->cmd = cpu_to_le32(SMB2_CRTCTX_AAPL_SERVER_QUERY);
buf->reply_bitmap = cpu_to_le64(reply_bitmap);
buf->server_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) ?
cpu_to_le64(AAPL_SERVER_CAPS_KSMBD) : 0;
buf->vol_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) ?
cpu_to_le64(vol_caps) : 0;
if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) {
__le32 *p = (__le32 *)((u8 *)buf + sizeof(*buf));
__le16 *model_str = (__le16 *)(p + 2);
const char *src = server_conf.aapl_model[0] ?
server_conf.aapl_model : "Xserve";
int i, model_bytes = 0;
/* Convert ASCII model string to UTF-16LE in-place */
for (i = 0; src[i] && i < AAPL_MODEL_MAX_CHARS; i++) {
model_str[i] = cpu_to_le16((unsigned char)src[i]);
model_bytes += 2;
}
p[0] = 0; /* pad2 */
p[1] = cpu_to_le32(model_bytes);
/* Update DataLength to reflect actual model string size */
buf->ccontext.DataLength =
cpu_to_le32(data_len - AAPL_MODEL_UTF16_BYTES + model_bytes);
}
}
/*
* Find lease object(opinfo) for given lease key/fid from lease
* break/file close path.

View File

@ -125,6 +125,7 @@ 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);
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);

View File

@ -48,6 +48,8 @@ struct ksmbd_server_config {
char *conf[SERVER_CONF_WORK_GROUP + 1];
struct task_struct *dh_task;
bool bind_interfaces_only;
/* AAPL model string for Finder icon, e.g. "Xserve" */
char aapl_model[32];
};
extern struct ksmbd_server_config server_conf;

View File

@ -37,6 +37,7 @@ static struct smb_version_values smb21_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
.create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb30_server_values = {
@ -64,6 +65,7 @@ static struct smb_version_values smb30_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
.create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb302_server_values = {
@ -91,6 +93,7 @@ static struct smb_version_values smb302_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
.create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb311_server_values = {
@ -118,6 +121,7 @@ static struct smb_version_values smb311_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
.create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_ops smb2_0_server_ops = {

View File

@ -3351,6 +3351,8 @@ int smb2_open(struct ksmbd_work *work)
int rc = 0;
int contxt_cnt = 0, query_disk_id = 0;
bool maximal_access_ctxt = false, posix_ctxt = false;
bool aapl_ctxt = false;
__u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
int s_type = 0;
int next_off = 0;
char *name = NULL;
@ -4124,7 +4126,32 @@ int smb2_open(struct ksmbd_work *work)
query_disk_id = 1;
}
if (conn->is_aapl == false) {
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_TIME_MACHINE)) {
context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
goto err_out1;
} else if (context) {
struct aapl_server_query_req *aapl_req;
if (le32_to_cpu(context->DataLength) <
sizeof(struct aapl_server_query_req)) {
rc = -EINVAL;
goto err_out1;
}
aapl_req = (struct aapl_server_query_req *)
((char *)context +
le16_to_cpu(context->DataOffset));
if (le32_to_cpu(aapl_req->cmd) ==
SMB2_CRTCTX_AAPL_SERVER_QUERY) {
conn->is_aapl = true;
aapl_ctxt = true;
aapl_req_bitmap = le64_to_cpu(aapl_req->req_bitmap);
aapl_client_caps = le64_to_cpu(aapl_req->client_caps);
}
}
} else if (conn->is_aapl == false) {
context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
@ -4338,6 +4365,10 @@ int smb2_open(struct ksmbd_work *work)
}
if (posix_ctxt) {
struct create_context *posix_ccontext;
posix_ccontext = (struct create_context *)(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength));
contxt_cnt++;
create_posix_rsp_buf(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength),
@ -4347,6 +4378,29 @@ int smb2_open(struct ksmbd_work *work)
iov_len += conn->vals->create_posix_size;
if (next_ptr)
*next_ptr = cpu_to_le32(next_off);
next_ptr = &posix_ccontext->Next;
next_off = conn->vals->create_posix_size;
}
/*
* AAPL create context response: see smb2pdu.h for the capability
* rationale. Scoped to TIME_MACHINE shares only.
*/
if (aapl_ctxt) {
if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
conn->aapl_readdir_attr = true;
contxt_cnt++;
create_aapl_rsp_buf(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength),
SMB2_CRTCTX_AAPL_FULL_SYNC,
aapl_req_bitmap);
le32_add_cpu(&rsp->CreateContextsLength,
conn->vals->create_aapl_size);
iov_len += conn->vals->create_aapl_size;
if (next_ptr)
*next_ptr = cpu_to_le32(next_off);
/* AAPL is last; next_ptr need not be updated */
}
if (contxt_cnt > 0) {

View File

@ -66,6 +66,65 @@ struct preauth_integrity_info {
/* Apple Defined Contexts */
#define SMB2_CREATE_AAPL "AAPL"
/*
* AAPL SMB2 extension -- kAAPL_SERVER_QUERY create context.
*
* Command code and bitmap values are the existing
* SMB2_CRTCTX_AAPL_* constants in fs/smb/common/smb2pdu.h.
*
* Omitting the model string when reply_bitmap includes
* SMB2_CRTCTX_AAPL_MODEL_INFO causes smbfs.kext to enter a broken
* disconnect path requiring a reboot.
*
* Layout: ccontext(16) + Name[4] + Pad[4] + cmd(4) + reserved(4) +
* reply_bitmap(8) + server_caps(8) + vol_caps(8)
* When MODEL_INFO requested, appended: pad2(4) + model_bytes(4) + UTF-16LE
*/
#define SMB2_CREATE_AAPL_LEN 4
/*
* Server capability flags (server_caps field) -- SMB2_CRTCTX_AAPL_UNIX_BASED:
* prevents macOS Windows-compat mode (question-mark icons).
* SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE: enables server-side file copy via
* FSCTL_SRV_COPYCHUNK. SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR: inline
* FinderInfo per FIND entry, set when client also advertises the bit;
* format: EaSize=max_access, ShortName[0..7]=rfork_size,
* ShortName[8..23]=FinderInfo(16B), Reserved2=unix_mode.
*/
#define AAPL_SERVER_CAPS_KSMBD (SMB2_CRTCTX_AAPL_UNIX_BASED | \
SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE | \
SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
/* Model string: up to 31 ASCII chars */
#define AAPL_MODEL_MAX_CHARS 31
#define AAPL_MODEL_UTF16_BYTES (AAPL_MODEL_MAX_CHARS * 2)
/*
* Max AAPL response: header(24) + base data(32) + pad2(4) + model_bytes(4)
* + model(62), 8-byte aligned: ALIGN(126, 8) = 128 bytes.
*/
#define AAPL_RSP_MAX_SIZE 128
/* AAPL server query request (client->server) */
struct aapl_server_query_req {
__le32 cmd;
__le32 reserved;
__le64 req_bitmap;
__le64 client_caps;
} __packed;
struct create_aapl_rsp {
struct create_context_hdr ccontext;
__u8 Name[4];
__u8 Pad[4];
__le32 cmd;
__le32 reserved;
__le64 reply_bitmap;
__le64 server_caps;
__le64 vol_caps;
/* when MODEL_INFO requested: __le32 pad2; __le32 model_bytes; __le16 model[] */
} __packed;
#define DURABLE_HANDLE_MAX_TIMEOUT 300000
struct create_alloc_size_req {

View File

@ -322,6 +322,15 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
goto out;
}
server_conf.share_fake_fscaps = req->share_fake_fscaps;
/* AAPL model string for Finder icon */
if (req->aapl_model[0])
strscpy(server_conf.aapl_model, req->aapl_model,
sizeof(server_conf.aapl_model));
else
strscpy(server_conf.aapl_model, "Xserve",
sizeof(server_conf.aapl_model));
ksmbd_init_domain(req->sub_auth);
if (req->smb2_max_read)