* [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids
@ 2026-10-28 6:19 Jan Koester
2026-10-28 6:19 ` [RFC PATCH 1/6] ksmbd: don't ignore the owner and group of a descriptor without a DACL Jan Koester
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:19 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
Hi,
Windows names owners and ACL entries by SID; for a domain account that
is its objectSid. ksmbd shows a file's owner, group and POSIX ACL
entries as S-1-22 SIDs or as its server domain SID + uid, which a
Windows client can't relate to the domain's accounts, and a SID a client
sets for a domain account doesn't map back to an id at all. This series
lets user space, which knows the directory, translate between ids and
SIDs in both directions, for accounts that are logged in or not.
It applies on top of the UUID/GUID id RFC (uid_r v2,
<cover.1792552161.git.jan.koester@tuxist.de>) and the NTLM pass-through
series (<cover.1792345559.git.jan.koester@gmx.net>); the netlink events
it adds follow theirs. Patches 1-3 are independent of uid_r in substance;
I can rebase them onto mainline to go on their own if that is preferred.
1/6 ksmbd ignored the owner and group of a descriptor without a DACL,
which is what Windows sends to take ownership (and smbcacls -C/-G),
and still reported success.
2/6 After setting a POSIX ACL from a DACL, the following chmod used the
owning group's bits as the mask and left named entries without
effect.
3/6 passes the domain of an NTLM login (NTLMSSP DomainName) to user
space in the formerly reserved room of the login request, so a user
space serving several domains can tell DOMAIN\user from OTHER\user.
4/6 adds KSMBD_EVENT_SID_MAP_REQUEST/RESPONSE for a user space that sets
KSMBD_GLOBAL_FLAG_SID_MAP at startup: the SID of an id ksmbd shows,
the id of a domain SID a client sets. Answers are cached (mappings
5 minutes, "none" 1 minute, "couldn't say" never). Showing a
descriptor never fails on a missing mapping; setting one fails with
STATUS_NO_LOGON_SERVERS, STATUS_INVALID_OWNER or
STATUS_INVALID_PRIMARY_GROUP instead of being applied in part or
ignored. The session user also matches ACEs for its groups' SIDs,
like a Windows access token.
5/6 shows classic users as S-1-22-1-<uid> instead of the per-server
domain SID + uid, and still takes the old form.
6/6 documents it in Documentation/userspace-api/uid_r.rst.
The user space side is sharedd (FenrirOS' ksmbd user space) with
adconnect for the directory lookups. Tested in a VM against a directory
server: owner and group of accounts that aren't logged in, ownership and
group changes, DACL round trips, a session getting access only through
a nested group, inherited CREATOR OWNER ACEs, and the directory going
away (cached mappings stay, new ones fail cleanly).
Questions:
- Is a netlink round trip per uncached id acceptable here, or would you
rather have user space push the mappings of a session's ids at login
only? The latter can't cover owners that aren't logged in.
- compare_sids() matches a SID against any prefix of it; the token uses
an exact comparison. Should the existing callers move to that too?
Thanks,
Jan
Jan Koester (6):
ksmbd: don't ignore the owner and group of a descriptor without a DACL
ksmbd: keep named ACL entries effective when a DACL sets a POSIX ACL
ksmbd: pass the domain of an NTLM login to user space
ksmbd: map user and group ids to Windows SIDs through user space
ksmbd: show classic users as S-1-22-1-<uid> on every server
Documentation: uid_r: describe ksmbd's SID mapping
Documentation/userspace-api/uid_r.rst | 18 +-
fs/smb/server/Makefile | 2 +-
fs/smb/server/idmap.c | 320 ++++++++++++++++++++++++++
fs/smb/server/idmap.h | 25 ++
fs/smb/server/ksmbd_netlink.h | 62 ++++-
fs/smb/server/mgmt/user_config.c | 21 +-
fs/smb/server/mgmt/user_config.h | 7 +-
fs/smb/server/server.c | 2 +
fs/smb/server/smb2pdu.c | 35 ++-
fs/smb/server/smbacl.c | 256 ++++++++++++++++++---
fs/smb/server/smbacl.h | 18 +-
fs/smb/server/transport_ipc.c | 72 +++++-
fs/smb/server/transport_ipc.h | 5 +-
13 files changed, 793 insertions(+), 50 deletions(-)
create mode 100644 fs/smb/server/idmap.c
create mode 100644 fs/smb/server/idmap.h
base-commit: bdd1d550e3f518d445d199c8ee73dc962c5abab8
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 1/6] ksmbd: don't ignore the owner and group of a descriptor without a DACL
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
@ 2026-10-28 6:19 ` Jan Koester
2026-10-28 6:19 ` [RFC PATCH 2/6] ksmbd: keep named ACL entries effective when a DACL sets a POSIX ACL Jan Koester
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:19 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
parse_sec_desc() returned before looking at the owner and group SIDs
when the descriptor had no DACL_PRESENT, so an SMB2 SET_INFO with only
OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION -- what Windows
sends to take ownership, and smbcacls -C/-G -- did nothing and still
reported success.
Map the owner and group in any case and parse the DACL only if there is
one. set_info_sec() then changes the mode and the POSIX ACLs only for a
descriptor with a DACL, and leaves the stored descriptor alone without
one: replacing it would drop its DACL, and the owner shown comes from
the inode anyway. This also covers the SD buffer create context, so its
type_check argument goes away.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
fs/smb/server/smb2pdu.c | 4 ++--
fs/smb/server/smbacl.c | 39 ++++++++++++++++++++++++++-------------
fs/smb/server/smbacl.h | 2 +-
3 files changed, 29 insertions(+), 16 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 230bea45de..8e47d725c1 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3661,7 +3661,7 @@ static int smb2_create_sd_buffer(struct ksmbd_work *work,
sizeof(struct create_sd_buf_req))
return -EINVAL;
return set_info_sec(work->conn, work->tcon, path, &sd_buf->ntsd,
- le32_to_cpu(sd_buf->ccontext.DataLength), true, false);
+ le32_to_cpu(sd_buf->ccontext.DataLength), false);
}
static void ksmbd_acls_fattr(struct smb_fattr *fattr,
@@ -8535,7 +8535,7 @@ static int smb2_set_info_sec(struct ksmbd_file *fp, int addition_info,
return -EACCES;
return set_info_sec(fp->conn, fp->tcon, &fp->filp->f_path, pntsd,
- buf_len, false, true);
+ buf_len, true);
}
/**
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 126264cc07..d1cf63a9fe 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1017,12 +1017,14 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
le32_to_cpu(pntsd->sacloffset), dacloffset);
pntsd_type = le16_to_cpu(pntsd->type);
- if (!(pntsd_type & DACL_PRESENT)) {
+ /*
+ * Without a DACL only the owner and group change (Windows sends that
+ * to take ownership): don't return before them.
+ */
+ if (pntsd_type & DACL_PRESENT)
+ pntsd->type = cpu_to_le16(DACL_PRESENT);
+ else
ksmbd_debug(SMB, "DACL_PRESENT in DACL type is not set\n");
- return rc;
- }
-
- pntsd->type = cpu_to_le16(DACL_PRESENT);
if (pntsd->osidoffset) {
if (le32_to_cpu(pntsd->osidoffset) < sizeof(struct smb_ntsd))
@@ -1060,6 +1062,9 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
}
}
+ if (!(pntsd_type & DACL_PRESENT))
+ return 0;
+
if ((pntsd_type & (DACL_AUTO_INHERITED | DACL_AUTO_INHERIT_REQ)) ==
(DACL_AUTO_INHERITED | DACL_AUTO_INHERIT_REQ))
pntsd->type |= cpu_to_le16(DACL_AUTO_INHERITED);
@@ -1768,18 +1773,21 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
- bool type_check, bool get_write)
+ bool get_write)
{
int rc;
struct smb_fattr fattr = {{0}};
struct inode *inode = d_inode(path->dentry);
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
struct iattr newattrs;
+ bool dacl;
fattr.cf_uid = INVALID_UID;
fattr.cf_gid = INVALID_GID;
fattr.cf_mode = inode->i_mode;
+ /* parse_sec_desc() rewrites type */
+ dacl = le16_to_cpu(pntsd->type) & DACL_PRESENT;
rc = parse_sec_desc(idmap, pntsd, ntsd_len, &fattr);
if (rc)
goto out;
@@ -1793,12 +1801,14 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
newattrs.ia_valid |= ATTR_GID;
newattrs.ia_gid = fattr.cf_gid;
}
- newattrs.ia_valid |= ATTR_MODE;
- newattrs.ia_mode = (inode->i_mode & ~0777) | (fattr.cf_mode & 0777);
-
- ksmbd_vfs_remove_acl_xattrs(idmap, path);
+ /* only the owner and group without a DACL: mode and ACLs stay */
+ if (dacl) {
+ newattrs.ia_valid |= ATTR_MODE;
+ newattrs.ia_mode = (inode->i_mode & ~0777) | (fattr.cf_mode & 0777);
+ ksmbd_vfs_remove_acl_xattrs(idmap, path);
+ }
/* Update posix acls */
- if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && fattr.cf_dacls) {
+ if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && dacl && fattr.cf_dacls) {
rc = set_posix_acl(idmap, path->dentry,
ACL_TYPE_ACCESS, fattr.cf_acls);
if (rc < 0)
@@ -1821,8 +1831,11 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
if (rc)
goto out;
- /* Check it only calling from SD BUFFER context */
- if (type_check && !(le16_to_cpu(pntsd->type) & DACL_PRESENT))
+ /*
+ * The stored descriptor keeps its DACL when only the owner or group
+ * changed: the owner shown is the inode's anyway.
+ */
+ if (!dacl)
goto out;
if (test_share_config_flag(tcon->share_conf, KSMBD_SHARE_FLAG_ACL_XATTR)) {
diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h
index b6a01f0d45..d4c9b679d8 100644
--- a/fs/smb/server/smbacl.h
+++ b/fs/smb/server/smbacl.h
@@ -99,7 +99,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
bool strict);
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
- bool type_check, bool get_write);
+ bool get_write);
void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid);
void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid);
void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid);
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 2/6] ksmbd: keep named ACL entries effective when a DACL sets a POSIX ACL
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
2026-10-28 6:19 ` [RFC PATCH 1/6] ksmbd: don't ignore the owner and group of a descriptor without a DACL Jan Koester
@ 2026-10-28 6:19 ` Jan Koester
2026-10-28 6:20 ` [RFC PATCH 3/6] ksmbd: pass the domain of an NTLM login to user space Jan Koester
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:19 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
parse_dacl() turns ACEs for other users into named POSIX ACL entries
with an rwx mask, but leaves the group bits of fattr->cf_mode as the
owning group's ACE made them. set_info_sec() sets the ACL first and
then the mode, and on an inode with an extended ACL the mode's group
bits are the mask: a DACL without an ACE for the owning group (as
Windows writes them) left the mask at --- and every named entry
without effect.
Put the mask into the mode's group bits when there are named entries.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
fs/smb/server/smbacl.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index d1cf63a9fe..b3daf65798 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -680,6 +680,12 @@ static int parse_dacl(struct mnt_idmap *idmap,
if (acl_state.users->n || acl_state.groups->n) {
acl_state.mask.allow = 0x07;
+ /*
+ * With named entries the mode's group bits are the ACL's mask:
+ * the chmod after setting the ACL mustn't mask them all off.
+ */
+ fattr->cf_mode = (fattr->cf_mode & ~0070) |
+ (acl_state.mask.allow << 3);
if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
fattr->cf_acls =
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 3/6] ksmbd: pass the domain of an NTLM login to user space
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
2026-10-28 6:19 ` [RFC PATCH 1/6] ksmbd: don't ignore the owner and group of a descriptor without a DACL Jan Koester
2026-10-28 6:19 ` [RFC PATCH 2/6] ksmbd: keep named ACL entries effective when a DACL sets a POSIX ACL Jan Koester
@ 2026-10-28 6:20 ` Jan Koester
2026-10-28 6:20 ` [RFC PATCH 4/6] ksmbd: map user and group ids to Windows SIDs through " Jan Koester
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:20 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
The login request carries only the user name, though NTLMSSP's
AUTHENTICATE message names the domain too. A user space serving several
domains (or local accounts next to a domain's) can't tell DOMAIN\user
apart from OTHER\user, picks the first account of that name, and a
session that the domain controller then authenticates as one domain's
user runs with another account's ids.
Put the domain into the formerly reserved room of struct
ksmbd_login_request, flagged with KSMBD_LOGIN_REQ_DOMAIN, so the
structure keeps its size and older user space keeps working. Ask for
the login response extension by the account name user space answered
with: that one is unambiguous, the client's may not be.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
fs/smb/server/ksmbd_netlink.h | 12 +++++++++++-
fs/smb/server/mgmt/user_config.c | 16 ++++++++++++----
fs/smb/server/mgmt/user_config.h | 2 +-
fs/smb/server/smb2pdu.c | 20 ++++++++++++++++----
fs/smb/server/transport_ipc.c | 12 +++++++++++-
fs/smb/server/transport_ipc.h | 2 +-
6 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 3fa1c7f801..3df5264ad7 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -66,6 +66,7 @@
#define KSMBD_GENL_VERSION 0x01
#define KSMBD_REQ_MAX_ACCOUNT_NAME_SZ 48
+#define KSMBD_REQ_MAX_DOMAIN_NAME_SZ 48
#define KSMBD_REQ_MAX_HASH_SZ 18
#define KSMBD_REQ_MAX_SHARE_NAME 64
@@ -140,9 +141,18 @@ struct ksmbd_shutdown_request {
struct ksmbd_login_request {
__u32 handle;
__s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
- __u32 reserved[16]; /* Reserved room */
+ __u32 flags; /* KSMBD_LOGIN_REQ_* */
+ __s8 domain[KSMBD_REQ_MAX_DOMAIN_NAME_SZ]; /* with _DOMAIN */
+ __u32 reserved[3]; /* Reserved room */
};
+/*
+ * The domain the client named with the account (NTLMSSP DomainName): user
+ * space serving several domains can tell their accounts of the same name
+ * apart. Formerly reserved, so zero from older ksmbd.
+ */
+#define KSMBD_LOGIN_REQ_DOMAIN BIT(0)
+
/*
* IPC user login response.
*/
diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c
index cb175a4c78..a25eb0647c 100644
--- a/fs/smb/server/mgmt/user_config.c
+++ b/fs/smb/server/mgmt/user_config.c
@@ -9,21 +9,29 @@
#include "user_config.h"
#include "../transport_ipc.h"
-struct ksmbd_user *ksmbd_login_user(const char *account)
+struct ksmbd_user *ksmbd_login_user(const char *account, const char *domain)
{
struct ksmbd_login_response *resp;
struct ksmbd_login_response_ext *resp_ext = NULL;
struct ksmbd_user *user = NULL;
- resp = ksmbd_ipc_login_request(account);
+ resp = ksmbd_ipc_login_request(account, domain);
if (!resp)
return NULL;
if (!(resp->status & KSMBD_USER_FLAG_OK))
goto out;
- if (resp->status & KSMBD_USER_FLAG_EXTENSION)
- resp_ext = ksmbd_ipc_login_request_ext(account);
+ /*
+ * The extension of the account user space found: the name it
+ * answered with is unambiguous, the one the client gave may not be.
+ */
+ if (resp->status & KSMBD_USER_FLAG_EXTENSION) {
+ resp->account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ - 1] = '\0';
+ resp_ext = ksmbd_ipc_login_request_ext(resp->account[0] ?
+ (const char *)resp->account :
+ account);
+ }
user = ksmbd_alloc_user(resp, resp_ext);
kvfree(resp_ext);
diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h
index e55470e008..0035675e03 100644
--- a/fs/smb/server/mgmt/user_config.h
+++ b/fs/smb/server/mgmt/user_config.h
@@ -63,7 +63,7 @@ static inline unsigned int user_gid(struct ksmbd_user *user)
return user->gid;
}
-struct ksmbd_user *ksmbd_login_user(const char *account);
+struct ksmbd_user *ksmbd_login_user(const char *account, const char *domain);
struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
struct ksmbd_login_response_ext *resp_ext);
void ksmbd_free_user(struct ksmbd_user *user);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 8e47d725c1..db7f677b07 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2169,8 +2169,8 @@ static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
{
struct authenticate_message *authblob;
struct ksmbd_user *user;
- char *name;
- unsigned int name_off, name_len, secbuf_len;
+ char *name, *domain = NULL;
+ unsigned int name_off, name_len, dom_off, dom_len, secbuf_len;
if (conn->use_spnego && conn->mechToken)
secbuf_len = conn->mechTokenLen;
@@ -2196,8 +2196,20 @@ static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
return NULL;
}
- ksmbd_debug(SMB, "session setup request for user %s\n", name);
- user = ksmbd_login_user(name);
+ /* DOMAIN\user: which of several domains' user of that name */
+ dom_off = le32_to_cpu(authblob->DomainName.BufferOffset);
+ dom_len = le16_to_cpu(authblob->DomainName.Length);
+ if (dom_len && secbuf_len >= (u64)dom_off + dom_len) {
+ domain = smb_strndup_from_utf16((const char *)authblob + dom_off,
+ dom_len, true, conn->local_nls);
+ if (IS_ERR(domain))
+ domain = NULL;
+ }
+
+ ksmbd_debug(SMB, "session setup request for user %s domain %s\n",
+ name, domain ? domain : "");
+ user = ksmbd_login_user(name, domain);
+ kfree(domain);
kfree(name);
return user;
}
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 7bbe1b3607..4d2f16b96e 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -648,7 +648,12 @@ static int ksmbd_ipc_heartbeat_request(void)
return ret;
}
-struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account)
+/*
+ * @domain: the domain the client named (NULL or "": none); one too long to
+ * pass is left out, user space then decides by the account alone
+ */
+struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account,
+ const char *domain)
{
struct ksmbd_ipc_msg *msg;
struct ksmbd_login_request *req;
@@ -665,6 +670,11 @@ struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account)
req = (struct ksmbd_login_request *)msg->payload;
req->handle = ksmbd_acquire_id(&ipc_ida);
strscpy(req->account, account, KSMBD_REQ_MAX_ACCOUNT_NAME_SZ);
+ if (domain && domain[0] &&
+ strlen(domain) < KSMBD_REQ_MAX_DOMAIN_NAME_SZ) {
+ req->flags |= KSMBD_LOGIN_REQ_DOMAIN;
+ strscpy(req->domain, domain, KSMBD_REQ_MAX_DOMAIN_NAME_SZ);
+ }
resp = ipc_msg_send_request(msg, req->handle);
ipc_msg_handle_free(req->handle);
diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
index f957a33ae3..1aefa23264 100644
--- a/fs/smb/server/transport_ipc.h
+++ b/fs/smb/server/transport_ipc.h
@@ -11,7 +11,7 @@
#define KSMBD_IPC_MAX_PAYLOAD 4096
struct ksmbd_login_response *
-ksmbd_ipc_login_request(const char *account);
+ksmbd_ipc_login_request(const char *account, const char *domain);
struct ksmbd_login_response_ext *
ksmbd_ipc_login_request_ext(const char *account);
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 4/6] ksmbd: map user and group ids to Windows SIDs through user space
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
` (2 preceding siblings ...)
2026-10-28 6:20 ` [RFC PATCH 3/6] ksmbd: pass the domain of an NTLM login to user space Jan Koester
@ 2026-10-28 6:20 ` Jan Koester
2026-10-28 6:20 ` [RFC PATCH 5/6] ksmbd: show classic users as S-1-22-1-<uid> on every server Jan Koester
2026-10-28 6:20 ` [RFC PATCH 6/6] Documentation: uid_r: describe ksmbd's SID mapping Jan Koester
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:20 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
A domain account has a SID of its own (its objectSid) that Windows
clients use for owners and ACL entries, while files and credentials
carry the account's uid or gid -- a classic id, a UUID or a GUID. ksmbd
shows the ids as S-1-22 SIDs or its server domain SID + uid, which
Windows can't relate to the domain's accounts, and SIDs a client sets
for domain accounts don't map back at all.
Let user space translate in both directions. A user space that sets
KSMBD_GLOBAL_FLAG_SID_MAP at startup gets KSMBD_EVENT_SID_MAP_REQUEST
for the SID of an id ksmbd shows as owner, group or ACL entry, and for
the id of a domain account SID (S-1-5-21-..., outside the server's own
domain) a client sets -- for any account, logged in or not. Answers
are cached: mappings for five minutes, "no mapping" for one minute; an
answer user space couldn't give (its directory unreachable) is never
cached. Ids without a mapping keep the SIDs ksmbd gave them so far.
Showing a descriptor never fails on a missing mapping. Setting one
fails instead of being applied in part: STATUS_NO_LOGON_SERVERS when
user space can't answer, STATUS_INVALID_OWNER or
STATUS_INVALID_PRIMARY_GROUP for a domain SID that maps to no user or
group, which used to be ignored silently. ACEs for groups become
ACL_GROUP entries of the POSIX ACL.
A session matched only its user's own SID against the ACEs of a stored
descriptor. Give the session user the SIDs of its groups too, mapped
and unmapped, like a Windows access token, built at login, and compare
SIDs exactly: compare_sids() accepts a prefix.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
fs/smb/server/Makefile | 2 +-
fs/smb/server/idmap.c | 320 +++++++++++++++++++++++++++++++
fs/smb/server/idmap.h | 25 +++
fs/smb/server/ksmbd_netlink.h | 50 +++++
fs/smb/server/mgmt/user_config.c | 5 +
fs/smb/server/mgmt/user_config.h | 5 +
fs/smb/server/server.c | 2 +
fs/smb/server/smb2pdu.c | 11 +-
fs/smb/server/smbacl.c | 190 +++++++++++++++++-
fs/smb/server/smbacl.h | 16 +-
fs/smb/server/transport_ipc.c | 60 ++++++
fs/smb/server/transport_ipc.h | 3 +
12 files changed, 672 insertions(+), 17 deletions(-)
create mode 100644 fs/smb/server/idmap.c
create mode 100644 fs/smb/server/idmap.h
diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index a3e9306055..39a6ec4ca9 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -8,7 +8,7 @@ ksmbd-y := unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
misc.o oplock.o connection.o ksmbd_work.o crypto_ctx.o \
mgmt/ksmbd_ida.o mgmt/user_config.o mgmt/share_config.o \
mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
- transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
+ transport_tcp.o transport_ipc.o smbacl.o idmap.o smb2pdu.o \
smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
ksmbd_spnego_negtokentarg.asn1.o asn1.o compress.o
diff --git a/fs/smb/server/idmap.c b/fs/smb/server/idmap.c
new file mode 100644
index 0000000000..8ea83b3e93
--- /dev/null
+++ b/fs/smb/server/idmap.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The Windows SIDs of user and group ids, as user space maps them.
+ *
+ * A domain account has a SID of its own (its objectSid) that Windows uses
+ * for owners and ACL entries, while files and credentials carry the
+ * account's uid or gid -- a classic id, a UUID or a GUID. ksmbd asks user
+ * space (KSMBD_EVENT_SID_MAP_REQUEST) for every owner and ACL entry it
+ * shows or sets, also of accounts that aren't logged in, and keeps the
+ * answers here for a while: mappings for IDMAP_TTL, "no such mapping" for
+ * IDMAP_NEG_TTL. An answer that user space couldn't give (its directory
+ * unreachable) is never cached. Only SIDs of the form S-1-5-21-... (a
+ * domain's accounts) outside the server's own domain are looked up.
+ */
+
+#include <linux/hashtable.h>
+#include <linux/jhash.h>
+#include <linux/jiffies.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "glob.h"
+#include "idmap.h"
+#include "ksmbd_netlink.h"
+#include "server.h"
+#include "smbacl.h"
+#include "transport_ipc.h"
+
+#define IDMAP_TTL (300 * HZ)
+#define IDMAP_NEG_TTL (60 * HZ)
+#define IDMAP_MAX 4096
+#define IDMAP_BITS 9
+
+/*
+ * A mapping (id and SID, in both tables) or a negative entry for an id or
+ * a SID (in that one table).
+ */
+struct idmap_entry {
+ struct list_head lru;
+ struct hlist_node by_id;
+ struct hlist_node by_sid;
+ bool has_id;
+ bool has_sid;
+ bool group;
+ struct __kernel_uid_r id;
+ struct smb_sid sid;
+ unsigned long expires;
+};
+
+static DEFINE_HASHTABLE(idmap_by_id, IDMAP_BITS);
+static DEFINE_HASHTABLE(idmap_by_sid, IDMAP_BITS);
+static LIST_HEAD(idmap_lru);
+static unsigned int idmap_count;
+static DEFINE_SPINLOCK(idmap_lock);
+
+static const u8 nt_authority[NUM_AUTHS] = {0, 0, 0, 0, 0, 5};
+
+static size_t sid_size(const struct smb_sid *sid)
+{
+ return CIFS_SID_BASE_SIZE + sizeof(__le32) * sid->num_subauth;
+}
+
+static bool sid_eq(const struct smb_sid *a, const struct smb_sid *b)
+{
+ return a->num_subauth == b->num_subauth && !memcmp(a, b, sid_size(a));
+}
+
+/* only the member type selects, the rest zero: comparable with memcmp() */
+static void id_norm(struct __kernel_uid_r *out, const struct __kernel_uid_r *in)
+{
+ memset(out, 0, sizeof(*out));
+ out->type = in->type;
+ if (in->type == UID_R_LEGACY)
+ out->legacy = in->legacy;
+ else if (in->type == UID_R_UUID || in->type == UID_R_GUID)
+ memcpy(out->uuid, in->uuid, sizeof(out->uuid));
+}
+
+static u32 id_hash(bool group, const struct __kernel_uid_r *id)
+{
+ return jhash(id, sizeof(*id), group);
+}
+
+static u32 sid_hash(const struct smb_sid *sid)
+{
+ return jhash(sid, sid_size(sid), 0);
+}
+
+bool ksmbd_idmap_enabled(void)
+{
+ return server_conf.flags & KSMBD_GLOBAL_FLAG_SID_MAP;
+}
+
+/* S-1-5-21-..., not of the server's own domain */
+bool ksmbd_idmap_sid_mappable(const struct smb_sid *sid)
+{
+ const struct smb_sid *own = &server_conf.domain_sid;
+
+ if (sid->revision != 1 || sid->num_subauth < 2 ||
+ sid->num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ memcmp(sid->authority, nt_authority, NUM_AUTHS) ||
+ le32_to_cpu(sid->sub_auth[0]) != 21)
+ return false;
+ return !(sid->num_subauth == own->num_subauth + 1 &&
+ !memcmp(sid->sub_auth, own->sub_auth,
+ sizeof(__le32) * own->num_subauth));
+}
+
+/* with idmap_lock */
+static void idmap_unlink(struct idmap_entry *e)
+{
+ list_del(&e->lru);
+ if (e->has_id)
+ hash_del(&e->by_id);
+ if (e->has_sid)
+ hash_del(&e->by_sid);
+ idmap_count--;
+ kfree(e);
+}
+
+/* a copy of the cached entry for the id (true), or false */
+static bool idmap_find_id(bool group, const struct __kernel_uid_r *id,
+ struct idmap_entry *out)
+{
+ struct idmap_entry *e;
+ bool found = false;
+
+ spin_lock(&idmap_lock);
+ hash_for_each_possible(idmap_by_id, e, by_id, id_hash(group, id)) {
+ if (e->group != group || memcmp(&e->id, id, sizeof(*id)))
+ continue;
+ if (time_after(jiffies, e->expires)) {
+ idmap_unlink(e);
+ break;
+ }
+ list_move(&e->lru, &idmap_lru);
+ *out = *e;
+ found = true;
+ break;
+ }
+ spin_unlock(&idmap_lock);
+ return found;
+}
+
+static bool idmap_find_sid(const struct smb_sid *sid, struct idmap_entry *out)
+{
+ struct idmap_entry *e;
+ bool found = false;
+
+ spin_lock(&idmap_lock);
+ hash_for_each_possible(idmap_by_sid, e, by_sid, sid_hash(sid)) {
+ if (!sid_eq(&e->sid, sid))
+ continue;
+ if (time_after(jiffies, e->expires)) {
+ idmap_unlink(e);
+ break;
+ }
+ list_move(&e->lru, &idmap_lru);
+ *out = *e;
+ found = true;
+ break;
+ }
+ spin_unlock(&idmap_lock);
+ return found;
+}
+
+/*
+ * Cache a mapping (id and sid), or that id (sid NULL) or sid (id NULL) has
+ * none. It replaces what was cached for the same id or SID.
+ */
+static void idmap_add(bool group, const struct __kernel_uid_r *id,
+ const struct smb_sid *sid)
+{
+ struct idmap_entry *e, *old;
+ struct hlist_node *tmp;
+
+ e = kzalloc_obj(struct idmap_entry, KSMBD_DEFAULT_GFP);
+ if (!e)
+ return;
+ e->group = group;
+ e->has_id = id;
+ e->has_sid = sid;
+ if (id)
+ e->id = *id;
+ if (sid)
+ memcpy(&e->sid, sid, sid_size(sid));
+ e->expires = jiffies + (id && sid ? IDMAP_TTL : IDMAP_NEG_TTL);
+
+ spin_lock(&idmap_lock);
+ if (id)
+ hash_for_each_possible_safe(idmap_by_id, old, tmp, by_id,
+ id_hash(group, id))
+ if (old->group == group &&
+ !memcmp(&old->id, id, sizeof(*id)))
+ idmap_unlink(old);
+ if (sid)
+ hash_for_each_possible_safe(idmap_by_sid, old, tmp, by_sid,
+ sid_hash(sid))
+ if (sid_eq(&old->sid, sid))
+ idmap_unlink(old);
+ while (idmap_count >= IDMAP_MAX)
+ idmap_unlink(list_last_entry(&idmap_lru, struct idmap_entry,
+ lru));
+ if (id)
+ hash_add(idmap_by_id, &e->by_id, id_hash(group, id));
+ if (sid)
+ hash_add(idmap_by_sid, &e->by_sid, sid_hash(sid));
+ list_add(&e->lru, &idmap_lru);
+ idmap_count++;
+ spin_unlock(&idmap_lock);
+}
+
+/* the SID of a response: well formed and one of a domain's accounts */
+static bool resp_sid(const struct ksmbd_sid_map_response *resp,
+ struct smb_sid *sid)
+{
+ const struct smb_sid *s = (const struct smb_sid *)resp->sid;
+
+ if (s->num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ !ksmbd_idmap_sid_mappable(s))
+ return false;
+ memset(sid, 0, sizeof(*sid));
+ memcpy(sid, s, sid_size(s));
+ return true;
+}
+
+int ksmbd_idmap_id_to_sid(bool group, const struct __kernel_uid_r *rid,
+ struct smb_sid *sid)
+{
+ struct ksmbd_sid_map_response *resp;
+ struct __kernel_uid_r id, rsp_id;
+ struct idmap_entry c;
+ struct smb_sid s;
+ int rc = -EAGAIN;
+
+ if (!ksmbd_idmap_enabled())
+ return -ENOENT;
+ id_norm(&id, rid);
+ if (idmap_find_id(group, &id, &c)) {
+ if (!c.has_sid)
+ return -ENOENT;
+ memcpy(sid, &c.sid, sizeof(*sid));
+ return 0;
+ }
+
+ resp = ksmbd_ipc_sid_map_request(KSMBD_SID_MAP_TO_SID |
+ (group ? KSMBD_SID_MAP_GROUP : 0),
+ &id, NULL, 0);
+ if (!resp)
+ return -EAGAIN;
+ id_norm(&rsp_id, &resp->id);
+ if (resp->status == KSMBD_SID_MAP_NONE) {
+ idmap_add(group, &id, NULL);
+ rc = -ENOENT;
+ } else if (resp->status == KSMBD_SID_MAP_OK &&
+ !!(resp->flags & KSMBD_SID_MAP_GROUP) == group &&
+ !memcmp(&rsp_id, &id, sizeof(id)) && resp_sid(resp, &s)) {
+ idmap_add(group, &id, &s);
+ memcpy(sid, &s, sizeof(*sid));
+ rc = 0;
+ } else if (resp->status == KSMBD_SID_MAP_OK) {
+ pr_err_ratelimited("bad SID map response for an id\n");
+ }
+ kvfree(resp);
+ return rc;
+}
+
+int ksmbd_idmap_sid_to_id(const struct smb_sid *sid, bool *group,
+ struct __kernel_uid_r *id)
+{
+ struct ksmbd_sid_map_response *resp;
+ struct idmap_entry c;
+ struct smb_sid s;
+ int rc = -EAGAIN;
+
+ if (!ksmbd_idmap_enabled() || !ksmbd_idmap_sid_mappable(sid))
+ return -ENOENT;
+ memset(&s, 0, sizeof(s));
+ memcpy(&s, sid, sid_size(sid));
+ if (idmap_find_sid(&s, &c)) {
+ if (!c.has_id)
+ return -ENOENT;
+ *group = c.group;
+ *id = c.id;
+ return 0;
+ }
+
+ resp = ksmbd_ipc_sid_map_request(0, NULL, &s, sid_size(&s));
+ if (!resp)
+ return -EAGAIN;
+ if (resp->status == KSMBD_SID_MAP_NONE) {
+ idmap_add(false, NULL, &s);
+ rc = -ENOENT;
+ } else if (resp->status == KSMBD_SID_MAP_OK &&
+ (resp->id.type == UID_R_LEGACY ||
+ resp->id.type == UID_R_UUID ||
+ resp->id.type == UID_R_GUID)) {
+ *group = resp->flags & KSMBD_SID_MAP_GROUP;
+ id_norm(id, &resp->id);
+ idmap_add(*group, id, &s);
+ rc = 0;
+ } else if (resp->status == KSMBD_SID_MAP_OK) {
+ pr_err_ratelimited("bad SID map response for a SID\n");
+ }
+ kvfree(resp);
+ return rc;
+}
+
+/* a new user space, or ksmbd going away: forget everything */
+void ksmbd_idmap_flush(void)
+{
+ struct idmap_entry *e, *tmp;
+
+ spin_lock(&idmap_lock);
+ list_for_each_entry_safe(e, tmp, &idmap_lru, lru)
+ idmap_unlink(e);
+ spin_unlock(&idmap_lock);
+}
diff --git a/fs/smb/server/idmap.h b/fs/smb/server/idmap.h
new file mode 100644
index 0000000000..47f432c3bb
--- /dev/null
+++ b/fs/smb/server/idmap.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * The Windows SIDs of user and group ids, as user space maps them
+ * (KSMBD_EVENT_SID_MAP_REQUEST), cached.
+ */
+
+#ifndef __KSMBD_IDMAP_H__
+#define __KSMBD_IDMAP_H__
+
+#include <linux/types.h>
+#include <linux/uidgid_r.h>
+
+struct smb_sid;
+
+bool ksmbd_idmap_enabled(void);
+bool ksmbd_idmap_sid_mappable(const struct smb_sid *sid);
+/* 0 and *sid, -ENOENT: no SID of its own, -EAGAIN: user space couldn't say */
+int ksmbd_idmap_id_to_sid(bool group, const struct __kernel_uid_r *id,
+ struct smb_sid *sid);
+/* 0 and *group, *id, -ENOENT: no account's SID, -EAGAIN: as above */
+int ksmbd_idmap_sid_to_id(const struct smb_sid *sid, bool *group,
+ struct __kernel_uid_r *id);
+void ksmbd_idmap_flush(void);
+
+#endif /* __KSMBD_IDMAP_H__ */
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 3df5264ad7..c12b4295ed 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -60,6 +60,12 @@
* This event is to make NTLM authentication of a user the login response
* marked KSMBD_USER_FLAG_PASSTHROUGH to be processed in userspace, e.g.
* by a domain controller.
+ *
+ * - KSMBD_EVENT_SID_MAP_REQUEST/RESPONSE(ksmbd_sid_map_request/response)
+ * This event is to translate between the id of a user or group and its
+ * Windows SID in userspace, e.g. with a directory, for the owners and
+ * ACL entries of security descriptors. Sent only to a user space that
+ * set KSMBD_GLOBAL_FLAG_SID_MAP.
*/
#define KSMBD_GENL_NAME "SMBD_GENL"
@@ -86,6 +92,7 @@ struct ksmbd_heartbeat {
#define KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL BIT(2)
#define KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF BIT(3)
#define KSMBD_GLOBAL_FLAG_DURABLE_HANDLE BIT(4)
+#define KSMBD_GLOBAL_FLAG_SID_MAP BIT(5)
/*
* IPC request for ksmbd server startup
@@ -221,6 +228,46 @@ struct ksmbd_ntlm_authen_response {
__u32 reserved[16]; /* Reserved room */
};
+/*
+ * IPC request to translate between a user's or group's id and its SID.
+ *
+ * With KSMBD_SID_MAP_TO_SID, id is the uid (or, with KSMBD_SID_MAP_GROUP,
+ * the gid) whose SID is wanted; otherwise sid is a binary SID (revision,
+ * sub-authority count, 48 bit authority big endian, sub-authorities little
+ * endian) whose id is wanted. The account need not be logged in.
+ */
+#define KSMBD_SID_MAP_TO_SID BIT(0)
+#define KSMBD_SID_MAP_GROUP BIT(1)
+
+#define KSMBD_SID_MAP_SID_SZ 68 /* 8 + 15 sub-authorities */
+
+struct ksmbd_sid_map_request {
+ __u32 handle;
+ __u32 flags; /* KSMBD_SID_MAP_TO_SID, _GROUP */
+ __kernel_uid_r_t id;
+ __u8 sid[KSMBD_SID_MAP_SID_SZ];
+ __u32 reserved[16]; /* Reserved room */
+};
+
+/*
+ * IPC response to a SID map request: the id and the SID of the account,
+ * KSMBD_SID_MAP_GROUP in flags if it is a group. KSMBD_SID_MAP_NONE: the
+ * id has no SID of its own, or the SID belongs to no account (ksmbd may
+ * cache that); KSMBD_SID_MAP_UNAVAILABLE: user space couldn't say now.
+ */
+#define KSMBD_SID_MAP_OK 0
+#define KSMBD_SID_MAP_NONE 1
+#define KSMBD_SID_MAP_UNAVAILABLE 2
+
+struct ksmbd_sid_map_response {
+ __u32 handle;
+ __u32 status; /* KSMBD_SID_MAP_OK, _NONE, ... */
+ __u32 flags; /* KSMBD_SID_MAP_GROUP */
+ __kernel_uid_r_t id;
+ __u8 sid[KSMBD_SID_MAP_SID_SZ];
+ __u32 reserved[16]; /* Reserved room */
+};
+
/*
* IPC request to fetch net share config.
*/
@@ -389,6 +436,9 @@ enum ksmbd_event {
KSMBD_EVENT_NTLM_AUTHEN_REQUEST,
KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
+ KSMBD_EVENT_SID_MAP_REQUEST = 20,
+ KSMBD_EVENT_SID_MAP_RESPONSE,
+
__KSMBD_EVENT_MAX,
KSMBD_EVENT_MAX = __KSMBD_EVENT_MAX - 1
};
diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c
index a25eb0647c..0ff3fc55d0 100644
--- a/fs/smb/server/mgmt/user_config.c
+++ b/fs/smb/server/mgmt/user_config.c
@@ -8,6 +8,7 @@
#include "user_config.h"
#include "../transport_ipc.h"
+#include "../smbacl.h"
struct ksmbd_user *ksmbd_login_user(const char *account, const char *domain)
{
@@ -73,6 +74,8 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
user->ngroups = 0;
user->sgid = NULL;
+ user->nsids = 0;
+ user->sids = NULL;
if (!user->name || !user->passkey)
goto err_free;
@@ -114,6 +117,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
ksmbd_debug(SMB, "supplementary groups : %d\n", user->ngroups);
}
+ ksmbd_user_set_token(user);
return user;
err_free:
@@ -127,6 +131,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
void ksmbd_free_user(struct ksmbd_user *user)
{
ksmbd_ipc_logout_request(user->name, user->flags);
+ kfree(user->sids);
kfree(user->sgid);
kfree(user->name);
kfree_sensitive(user->passkey);
diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h
index 0035675e03..9b6a73580f 100644
--- a/fs/smb/server/mgmt/user_config.h
+++ b/fs/smb/server/mgmt/user_config.h
@@ -8,6 +8,8 @@
#include "../glob.h"
+struct smb_sid;
+
struct ksmbd_user {
unsigned short flags;
@@ -22,6 +24,9 @@ struct ksmbd_user {
char *passkey;
int ngroups;
kgid_t *sgid;
+
+ int nsids; /* ksmbd_user_set_token() */
+ struct smb_sid *sids;
};
static inline bool user_guest(struct ksmbd_user *user)
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 0827c8c510..c6dc243011 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -14,6 +14,7 @@
#include <linux/moduleparam.h>
#include "server.h"
+#include "idmap.h"
#include "smb_common.h"
#include "smb2pdu.h"
#include "../common/smb2status.h"
@@ -746,6 +747,7 @@ static void __exit ksmbd_server_exit(void)
*/
ksmbd_conn_wq_destroy();
ksmbd_release_inode_hash();
+ ksmbd_idmap_flush();
}
MODULE_AUTHOR("Namjae Jeon <linkinjeon@kernel.org>");
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index db7f677b07..41d2bd732f 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4606,14 +4606,14 @@ int smb2_open(struct ksmbd_work *work)
if (file_present && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = smb_check_perm_dacl(conn, &path, &daccess,
req->DesiredAccess,
- sess->user->kuid, false);
+ sess->user, false);
if (rc)
goto err_out;
if (maximal_access_ctxt) {
maximal_access = FILE_MAXIMAL_ACCESS_LE;
rc = smb_check_perm_dacl(conn, &path, &maximal_access,
- 0, sess->user->kuid, false);
+ 0, sess->user, false);
if (rc)
goto err_out;
@@ -4799,8 +4799,10 @@ int smb2_open(struct ksmbd_work *work)
ksmbd_debug(SMB, "inherit posix acl failed : %d\n", posix_acl_rc);
rc = smb2_create_sd_buffer(work, req, &path);
- if (rc && rc != -ENOENT)
+ if (rc && rc != -ENOENT) {
+ rsp->hdr.Status = ksmbd_sd_err_status(rc);
goto err_out;
+ }
if (rc == -ENOENT) {
if (test_share_config_flag(work->tcon->share_conf,
@@ -5866,7 +5868,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
path.dentry = dent;
rc = smb_check_perm_dacl(priv->work->conn, &path,
&daccess, daccess,
- priv->work->sess->user->kuid,
+ priv->work->sess->user,
true);
if (rc) {
dput(dent);
@@ -8622,6 +8624,7 @@ int smb2_set_info(struct ksmbd_work *work)
le32_to_cpu(req->AdditionalInformation),
(char *)req + le16_to_cpu(req->BufferOffset),
le32_to_cpu(req->BufferLength));
+ rsp->hdr.Status = ksmbd_sd_err_status(rc);
break;
default:
rc = -EOPNOTSUPP;
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index b3daf65798..d6be65e3a9 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -16,7 +16,10 @@
#include "smb_common.h"
#include "server.h"
#include "misc.h"
+#include "../common/smb2status.h"
#include "mgmt/share_config.h"
+#include "mgmt/user_config.h"
+#include "idmap.h"
static const struct smb_sid domain = {1, 4, {0, 0, 0, 0, 0, 5},
{cpu_to_le32(21), cpu_to_le32(1), cpu_to_le32(2), cpu_to_le32(3),
@@ -296,8 +299,11 @@ static u32 sid_to_id_r(const struct smb_sid *psid, u8 *id)
return kind;
}
-/* id_to_sid() for any kuid: a classic one gets the usual SIDs */
-void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
+/*
+ * The SID ksmbd itself gives an id: a classic one the usual SIDs, a UUID
+ * or GUID S-1-22-<kind>-w0-w1-w2-w3.
+ */
+static void native_uid_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
{
if (uid_is_legacy(uid) || sidtype == SIDCREATOR_OWNER ||
sidtype == SIDCREATOR_GROUP)
@@ -308,7 +314,7 @@ void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
id_r_to_sid(SID_UNIX_GUID_USER, uid.guid.b, ssid);
}
-void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid)
+static void native_gid_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid)
{
if (gid_is_legacy(gid) || sidtype == SIDCREATOR_OWNER ||
sidtype == SIDCREATOR_GROUP)
@@ -319,6 +325,107 @@ void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid)
id_r_to_sid(SID_UNIX_GUID_GROUP, gid.guid.b, ssid);
}
+/* the account's own SID (a directory's objectSid) if user space maps it */
+static int mapped_uid_sid(kuid_t uid, struct smb_sid *ssid)
+{
+ struct __kernel_uid_r id;
+
+ if (uid_eq(uid, GLOBAL_ROOT_UID) || !from_kuid_r(&init_user_ns, uid, &id))
+ return -ENOENT;
+ return ksmbd_idmap_id_to_sid(false, &id, ssid);
+}
+
+static int mapped_gid_sid(kgid_t gid, struct smb_sid *ssid)
+{
+ struct __kernel_uid_r id;
+
+ if (gid_eq(gid, GLOBAL_ROOT_GID) || !from_kgid_r(&init_user_ns, gid, &id))
+ return -ENOENT;
+ return ksmbd_idmap_id_to_sid(true, &id, ssid);
+}
+
+/*
+ * The SID of an owner, a group or an ACL entry: the account's own SID if it
+ * has one (KSMBD_GLOBAL_FLAG_SID_MAP), else ksmbd's. A mapping user space
+ * can't give now leaves ksmbd's SID: showing a descriptor shouldn't fail.
+ */
+void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
+{
+ if ((sidtype == SIDOWNER || sidtype == SIDUNIX_USER) &&
+ !mapped_uid_sid(uid, ssid))
+ return;
+ native_uid_sid(uid, sidtype, ssid);
+}
+
+void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid)
+{
+ if (sidtype == SIDUNIX_GROUP && !mapped_gid_sid(gid, ssid))
+ return;
+ native_gid_sid(gid, sidtype, ssid);
+}
+
+/* the same SID: compare_sids() also takes a prefix */
+static bool sid_equal(const struct smb_sid *a, const struct smb_sid *b)
+{
+ return a->num_subauth == b->num_subauth && !compare_sids(a, b);
+}
+
+/*
+ * The SIDs a user's ACL entries may name, like a Windows access token: the
+ * user's and its groups', each the account's own SID (if user space maps
+ * one) and ksmbd's. Built at login; a
+ * mapping user space couldn't give then is missing for the session (its
+ * ACL entries don't match: access is denied, not granted).
+ */
+void ksmbd_user_set_token(struct ksmbd_user *user)
+{
+ int i, n = 0, max = 2 + 2 * (user->ngroups + 1);
+ struct smb_sid *sids;
+
+ sids = kcalloc(max, sizeof(*sids), KSMBD_DEFAULT_GFP);
+ if (!sids)
+ return;
+ if (!mapped_uid_sid(user->kuid, &sids[n]))
+ n++;
+ native_uid_sid(user->kuid, SIDOWNER, &sids[n++]);
+ for (i = -1; i < user->ngroups; i++) {
+ kgid_t gid = i < 0 ? user->kgid : user->sgid[i];
+
+ if (!mapped_gid_sid(gid, &sids[n]))
+ n++;
+ native_gid_sid(gid, SIDUNIX_GROUP, &sids[n++]);
+ }
+ user->sids = sids;
+ user->nsids = n;
+}
+
+/* whether an ACE's SID names the user (sid, without a token) */
+static bool sid_names_user(const struct ksmbd_user *user,
+ const struct smb_sid *sid, const struct smb_sid *ace)
+{
+ int i;
+
+ if (sid_equal(sid, ace))
+ return true;
+ for (i = 0; i < user->nsids; i++)
+ if (sid_equal(&user->sids[i], ace))
+ return true;
+ return false;
+}
+
+__le32 ksmbd_sd_err_status(int rc)
+{
+ switch (rc) {
+ case -KSMBD_SD_EOWNER:
+ return STATUS_INVALID_OWNER;
+ case -KSMBD_SD_EGROUP:
+ return STATUS_INVALID_PRIMARY_GROUP;
+ case -KSMBD_SD_EUNAVAIL:
+ return STATUS_NO_LOGON_SERVERS;
+ }
+ return 0;
+}
+
static int sid_to_id(struct mnt_idmap *idmap,
struct smb_sid *psid, uint sidtype,
struct smb_fattr *fattr)
@@ -377,6 +484,37 @@ static int sid_to_id(struct mnt_idmap *idmap,
return -EINVAL;
}
+ /* a domain account's own SID: the id user space maps it to */
+ {
+ struct __kernel_uid_r rid;
+ bool group;
+
+ rc = ksmbd_idmap_sid_to_id(psid, &group, &rid);
+ if (rc == -EAGAIN)
+ return -EAGAIN;
+ if (!rc && group != (sidtype != SIDOWNER))
+ return -EINVAL; /* a group's SID as owner, or so */
+ if (!rc && !group) {
+ kuid_t uid = make_kuid_r(&init_user_ns, &rid);
+
+ uid = from_vfsuid(idmap, &init_user_ns, VFSUIDT_INIT(uid));
+ if (!uid_valid(uid))
+ return -EINVAL;
+ fattr->cf_uid = uid;
+ return 0;
+ }
+ if (!rc) {
+ kgid_t gid = make_kgid_r(&init_user_ns, &rid);
+
+ gid = from_vfsgid(idmap, &init_user_ns, VFSGIDT_INIT(gid));
+ if (!gid_valid(gid))
+ return -EINVAL;
+ fattr->cf_gid = gid;
+ return 0;
+ }
+ rc = -EINVAL;
+ }
+
if (sidtype == SIDOWNER) {
kuid_t uid;
uid_t id;
@@ -624,10 +762,28 @@ static int parse_dacl(struct mnt_idmap *idmap,
acl_mode = access_flags_to_mode(fattr, ppace[i]->access_req,
ppace[i]->type);
temp_fattr.cf_uid = INVALID_UID;
+ temp_fattr.cf_gid = INVALID_GID;
ret = sid_to_id(idmap, &ppace[i]->sid, SIDOWNER, &temp_fattr);
- if (ret || uid_eq(temp_fattr.cf_uid, INVALID_UID)) {
- pr_err_ratelimited("%s: Error %d mapping Owner SID to uid\n",
- __func__, ret);
+ if (ret && ret != -EAGAIN)
+ ret = sid_to_id(idmap, &ppace[i]->sid, SIDUNIX_GROUP,
+ &temp_fattr);
+ /* not half the ACL in the inode, all of it in the xattr */
+ if (ret == -EAGAIN)
+ goto out;
+ if (ret) {
+ ksmbd_debug(SMB, "ACE SID has no uid or gid\n");
+ ret = 0;
+ continue;
+ }
+
+ if (!gid_eq(temp_fattr.cf_gid, INVALID_GID)) {
+ struct posix_ace_state_array *g = acl_state.groups;
+ struct posix_ace_state_array *dg = default_acl_state.groups;
+
+ g->aces[g->n].gid = temp_fattr.cf_gid;
+ g->aces[g->n++].perms.allow = (acl_mode & 0700) >> 6;
+ dg->aces[dg->n].gid = temp_fattr.cf_gid;
+ dg->aces[dg->n++].perms.allow = (acl_mode & 0700) >> 6;
continue;
}
@@ -1043,6 +1199,12 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
}
rc = sid_to_id(idmap, owner_sid_ptr, SIDOWNER, fattr);
+ if (rc == -EAGAIN)
+ return -KSMBD_SD_EUNAVAIL;
+ /* never "unchanged" for a domain account that has no user */
+ if (rc && ksmbd_idmap_enabled() &&
+ ksmbd_idmap_sid_mappable(owner_sid_ptr))
+ return -KSMBD_SD_EOWNER;
if (rc) {
ksmbd_debug(SMB, "Owner SID has no Unix uid mapping\n");
owner_sid_ptr = NULL;
@@ -1061,6 +1223,11 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
return rc;
}
rc = sid_to_id(idmap, group_sid_ptr, SIDUNIX_GROUP, fattr);
+ if (rc == -EAGAIN)
+ return -KSMBD_SD_EUNAVAIL;
+ if (rc && ksmbd_idmap_enabled() &&
+ ksmbd_idmap_sid_mappable(group_sid_ptr))
+ return -KSMBD_SD_EGROUP;
if (rc) {
ksmbd_debug(SMB, "Group SID has no Unix gid mapping\n");
group_sid_ptr = NULL;
@@ -1083,6 +1250,8 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
rc = parse_dacl(idmap, dacl_ptr, end_of_acl,
owner_sid_ptr, group_sid_ptr, fattr);
+ if (rc == -EAGAIN)
+ return -KSMBD_SD_EUNAVAIL;
if (rc)
return rc;
}
@@ -1550,9 +1719,10 @@ bool smb_inherit_flags(int flags, bool is_dir)
}
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, __le32 raw_daccess, kuid_t uid,
- bool strict)
+ __le32 *pdaccess, __le32 raw_daccess,
+ struct ksmbd_user *user, bool strict)
{
+ kuid_t uid = user->kuid;
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
struct smb_ntsd *pntsd = NULL;
struct smb_acl *pdacl;
@@ -1634,7 +1804,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
}
if (ace->flags & INHERIT_ONLY_ACE ||
- (compare_sids(&sid, &ace->sid) &&
+ (!sid_names_user(user, &sid, &ace->sid) &&
compare_sids(&sid_unix_NFS_mode, &ace->sid) &&
compare_sids(&sid_everyone, &ace->sid) &&
compare_sids(&sid_authusers, &ace->sid) &&
@@ -1683,7 +1853,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
sizeof(__le32) * ace->sid.num_subauth)
break;
- if (!compare_sids(&sid, &ace->sid) ||
+ if (sid_names_user(user, &sid, &ace->sid) ||
!compare_sids(&sid_unix_NFS_mode, &ace->sid)) {
found = 1;
break;
diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h
index d4c9b679d8..fdc70e2b6e 100644
--- a/fs/smb/server/smbacl.h
+++ b/fs/smb/server/smbacl.h
@@ -81,6 +81,16 @@ struct posix_acl_state {
struct posix_ace_state_array *groups;
};
+/*
+ * parse_sec_desc() / set_info_sec() errors about the SIDs of domain
+ * accounts (KSMBD_GLOBAL_FLAG_SID_MAP); ksmbd_sd_err_status() names them.
+ */
+#define KSMBD_SD_EOWNER EBADE /* owner SID: no such user */
+#define KSMBD_SD_EGROUP EBADR /* group SID: no such group */
+#define KSMBD_SD_EUNAVAIL EHOSTDOWN /* user space couldn't map now */
+
+__le32 ksmbd_sd_err_status(int rc);
+
int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
int acl_len, struct smb_fattr *fattr);
int build_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd,
@@ -94,9 +104,11 @@ int compare_sids(const struct smb_sid *ctsid, const struct smb_sid *cwsid);
bool smb_inherit_flags(int flags, bool is_dir);
int smb_inherit_dacl(struct ksmbd_conn *conn, const struct path *path,
kuid_t uid, kgid_t gid);
+struct ksmbd_user;
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, __le32 raw_daccess, kuid_t uid,
- bool strict);
+ __le32 *pdaccess, __le32 raw_daccess,
+ struct ksmbd_user *user, bool strict);
+void ksmbd_user_set_token(struct ksmbd_user *user);
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
bool get_write);
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 4d2f16b96e..7f8124333c 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -17,6 +17,7 @@
#include "vfs_cache.h"
#include "transport_ipc.h"
+#include "idmap.h"
#include "server.h"
#include "smb_common.h"
@@ -131,6 +132,10 @@ static const struct nla_policy ksmbd_nl_policy[KSMBD_EVENT_MAX + 1] = {
NLA_POLICY_MIN_LEN(sizeof(struct ksmbd_ntlm_authen_request)),
[KSMBD_EVENT_NTLM_AUTHEN_RESPONSE] =
NLA_POLICY_EXACT_LEN(sizeof(struct ksmbd_ntlm_authen_response)),
+ [KSMBD_EVENT_SID_MAP_REQUEST] =
+ NLA_POLICY_EXACT_LEN(sizeof(struct ksmbd_sid_map_request)),
+ [KSMBD_EVENT_SID_MAP_RESPONSE] =
+ NLA_POLICY_EXACT_LEN(sizeof(struct ksmbd_sid_map_response)),
};
static struct genl_ops ksmbd_genl_ops[] = {
@@ -214,6 +219,14 @@ static struct genl_ops ksmbd_genl_ops[] = {
.cmd = KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
.doit = handle_generic_event,
},
+ {
+ .cmd = KSMBD_EVENT_SID_MAP_REQUEST,
+ .doit = handle_unsupported_event,
+ },
+ {
+ .cmd = KSMBD_EVENT_SID_MAP_RESPONSE,
+ .doit = handle_generic_event,
+ },
};
static struct genl_family ksmbd_genl_family = {
@@ -327,6 +340,7 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
ksmbd_set_fd_limit(req->file_max);
server_conf.flags = req->flags;
+ ksmbd_idmap_flush(); /* a new user space may map otherwise */
server_conf.signing = req->signing;
server_conf.tcp_port = req->tcp_port;
server_conf.ipc_timeout = req->ipc_timeout * HZ;
@@ -567,6 +581,10 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
if (entry->msg_sz != sizeof(struct ksmbd_ntlm_authen_response))
return -EINVAL;
break;
+ case KSMBD_EVENT_SID_MAP_REQUEST:
+ if (entry->msg_sz != sizeof(struct ksmbd_sid_map_response))
+ return -EINVAL;
+ break;
case KSMBD_EVENT_LOGIN_REQUEST_EXT:
{
struct ksmbd_login_response_ext *resp = entry->response;
@@ -783,6 +801,48 @@ ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
return resp;
}
+/**
+ * ksmbd_ipc_sid_map_request() - have user space translate an id or a SID
+ * @flags: KSMBD_SID_MAP_TO_SID, KSMBD_SID_MAP_GROUP
+ * @id: with KSMBD_SID_MAP_TO_SID, the id
+ * @sid: without it, the binary SID
+ * @sid_len: its length, at most KSMBD_SID_MAP_SID_SZ
+ *
+ * Only for a user space that set KSMBD_GLOBAL_FLAG_SID_MAP.
+ *
+ * Return: the response (kvfree() it), NULL on error or timeout
+ */
+struct ksmbd_sid_map_response *
+ksmbd_ipc_sid_map_request(unsigned int flags, const struct __kernel_uid_r *id,
+ const void *sid, size_t sid_len)
+{
+ struct ksmbd_ipc_msg *msg;
+ struct ksmbd_sid_map_request *req;
+ struct ksmbd_sid_map_response *resp;
+
+ if (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SID_MAP) ||
+ sid_len > KSMBD_SID_MAP_SID_SZ)
+ return NULL;
+
+ msg = ipc_msg_alloc(sizeof(struct ksmbd_sid_map_request));
+ if (!msg)
+ return NULL;
+
+ msg->type = KSMBD_EVENT_SID_MAP_REQUEST;
+ req = (struct ksmbd_sid_map_request *)msg->payload;
+ req->handle = ksmbd_acquire_id(&ipc_ida);
+ req->flags = flags;
+ if (id)
+ req->id = *id;
+ if (sid)
+ memcpy(req->sid, sid, sid_len);
+
+ resp = ipc_msg_send_request(msg, req->handle);
+ ipc_msg_handle_free(req->handle);
+ ipc_msg_free(msg);
+ return resp;
+}
+
struct ksmbd_tree_connect_response *
ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
struct ksmbd_share_config *share,
diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
index 1aefa23264..390d9fa800 100644
--- a/fs/smb/server/transport_ipc.h
+++ b/fs/smb/server/transport_ipc.h
@@ -32,6 +32,9 @@ struct ksmbd_share_config_response *
ksmbd_ipc_share_config_request(const char *name);
struct ksmbd_spnego_authen_response *
ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len);
+struct ksmbd_sid_map_response *
+ksmbd_ipc_sid_map_request(unsigned int flags, const struct __kernel_uid_r *id,
+ const void *sid, size_t sid_len);
struct ksmbd_ntlm_authen_response *
ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
const unsigned short lens[5]);
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 5/6] ksmbd: show classic users as S-1-22-1-<uid> on every server
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
` (3 preceding siblings ...)
2026-10-28 6:20 ` [RFC PATCH 4/6] ksmbd: map user and group ids to Windows SIDs through " Jan Koester
@ 2026-10-28 6:20 ` Jan Koester
2026-10-28 6:20 ` [RFC PATCH 6/6] Documentation: uid_r: describe ksmbd's SID mapping Jan Koester
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:20 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
A classic uid appeared in security descriptors as the server's own
domain SID + uid. That domain SID comes from user space and differs from
server to server (ksmbd-tools and sharedd derive it per machine), so
the same owner had a different SID on every file server, and the RID
looked like a domain account's.
Show them as S-1-22-1-<uid>, the SID ksmbd already used for root and
that S-1-22-2-<gid> pairs with for groups. Keep taking the old form when
a client sets it, and keep it in the session's SIDs, so descriptors
stored before still match.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
fs/smb/server/smbacl.c | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index d6be65e3a9..769864d64e 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -300,14 +300,16 @@ static u32 sid_to_id_r(const struct smb_sid *psid, u8 *id)
}
/*
- * The SID ksmbd itself gives an id: a classic one the usual SIDs, a UUID
- * or GUID S-1-22-<kind>-w0-w1-w2-w3.
+ * The SID ksmbd itself gives an id: a classic uid S-1-22-1-<uid>, the same
+ * on every server (it used to be the server's own domain SID + uid), a
+ * classic gid S-1-22-2-<gid>, a UUID or GUID S-1-22-<kind>-w0-w1-w2-w3.
*/
static void native_uid_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
{
- if (uid_is_legacy(uid) || sidtype == SIDCREATOR_OWNER ||
- sidtype == SIDCREATOR_GROUP)
+ if (sidtype == SIDCREATOR_OWNER || sidtype == SIDCREATOR_GROUP)
id_to_sid(from_kuid_munged(&init_user_ns, uid), sidtype, ssid);
+ else if (uid_is_legacy(uid))
+ id_to_sid(from_kuid_munged(&init_user_ns, uid), SIDUNIX_USER, ssid);
else if (uid.type == KID_UUID)
id_r_to_sid(SID_UNIX_UUID_USER, uid.uuid.b, ssid);
else
@@ -373,13 +375,14 @@ static bool sid_equal(const struct smb_sid *a, const struct smb_sid *b)
/*
* The SIDs a user's ACL entries may name, like a Windows access token: the
* user's and its groups', each the account's own SID (if user space maps
- * one) and ksmbd's. Built at login; a
+ * one) and ksmbd's -- for a classic uid also the server's domain SID + uid
+ * of older ksmbd, which stored descriptors may hold. Built at login; a
* mapping user space couldn't give then is missing for the session (its
* ACL entries don't match: access is denied, not granted).
*/
void ksmbd_user_set_token(struct ksmbd_user *user)
{
- int i, n = 0, max = 2 + 2 * (user->ngroups + 1);
+ int i, n = 0, max = 3 + 2 * (user->ngroups + 1);
struct smb_sid *sids;
sids = kcalloc(max, sizeof(*sids), KSMBD_DEFAULT_GFP);
@@ -388,6 +391,9 @@ void ksmbd_user_set_token(struct ksmbd_user *user)
if (!mapped_uid_sid(user->kuid, &sids[n]))
n++;
native_uid_sid(user->kuid, SIDOWNER, &sids[n++]);
+ if (uid_is_legacy(user->kuid) && !uid_eq(user->kuid, GLOBAL_ROOT_UID))
+ id_to_sid(from_kuid_munged(&init_user_ns, user->kuid), SIDOWNER,
+ &sids[n++]);
for (i = -1; i < user->ngroups; i++) {
kgid_t gid = i < 0 ? user->kgid : user->sgid[i];
@@ -519,8 +525,15 @@ static int sid_to_id(struct mnt_idmap *idmap,
kuid_t uid;
uid_t id;
- /* Only the server domain RID has a local uid representation. */
- sid_prefix = &server_conf.domain_sid;
+ /*
+ * A local uid: S-1-22-1-<uid>, or the server's domain SID +
+ * uid that ksmbd used to show (stored descriptors have it).
+ */
+ if (psid->num_subauth == sid_unix_users.num_subauth + 1 &&
+ !compare_sids(psid, &sid_unix_users))
+ sid_prefix = &sid_unix_users;
+ else
+ sid_prefix = &server_conf.domain_sid;
if (psid->num_subauth != sid_prefix->num_subauth + 1 ||
compare_sids(psid, sid_prefix))
return -EINVAL;
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 6/6] Documentation: uid_r: describe ksmbd's SID mapping
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
` (4 preceding siblings ...)
2026-10-28 6:20 ` [RFC PATCH 5/6] ksmbd: show classic users as S-1-22-1-<uid> on every server Jan Koester
@ 2026-10-28 6:20 ` Jan Koester
5 siblings, 0 replies; 7+ messages in thread
From: Jan Koester @ 2026-10-28 6:20 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Christian Brauner,
linux-cifs, linux-fsdevel, samba-technical, linux-kernel
Name the SIDs ksmbd gives classic users and groups, and describe
KSMBD_EVENT_SID_MAP_REQUEST: what user space is asked, the caching and
what happens when it can't answer.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@tuxist.de>
---
Documentation/userspace-api/uid_r.rst | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/Documentation/userspace-api/uid_r.rst b/Documentation/userspace-api/uid_r.rst
index d16d4317f5..2e887c296d 100644
--- a/Documentation/userspace-api/uid_r.rst
+++ b/Documentation/userspace-api/uid_r.rst
@@ -167,7 +167,23 @@ SMB (ksmbd)
groups appear in security descriptors as
``S-1-22-<kind>-w0-w1-w2-w3``: kind 3 and 4 for a UUID user and group,
5 and 6 for a GUID user and group, w0 to w3 the 16 bytes of the id as
- big endian words.
+ big endian words; classic users as ``S-1-22-1-<uid>``, classic groups
+ as ``S-1-22-2-<gid>``.
+
+ A user space that sets ``KSMBD_GLOBAL_FLAG_SID_MAP`` at startup is
+ asked (``KSMBD_EVENT_SID_MAP_REQUEST``) for the Windows SID of every id
+ ksmbd shows as owner, group or ACL entry and for the id of every
+ domain SID (``S-1-5-21-...``) a client sets, also for accounts that
+ are not logged in. A mapped account appears with its own SID (e.g. a
+ directory's ``objectSid``), others keep the SIDs above. ksmbd caches
+ mappings for five minutes and "no mapping" for one; an answer user
+ space couldn't give is not cached: descriptors then show the SIDs
+ above, and setting an owner, group or ACL that needs a mapping fails
+ with ``STATUS_NO_LOGON_SERVERS`` instead of being applied in part. A
+ domain SID that maps to no user (as owner) or group (as group) is
+ refused with ``STATUS_INVALID_OWNER`` or ``STATUS_INVALID_PRIMARY_GROUP``
+ rather than ignored. A session's user matches ACL entries naming its
+ own or its groups' SIDs, mapped or not.
NFSv4
Owners and ACL principals that are UUIDs or GUIDs travel as their text
--
2.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-28 6:20 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 6:19 [RFC PATCH 0/6] ksmbd: Windows SIDs for user and group ids Jan Koester
2026-10-28 6:19 ` [RFC PATCH 1/6] ksmbd: don't ignore the owner and group of a descriptor without a DACL Jan Koester
2026-10-28 6:19 ` [RFC PATCH 2/6] ksmbd: keep named ACL entries effective when a DACL sets a POSIX ACL Jan Koester
2026-10-28 6:20 ` [RFC PATCH 3/6] ksmbd: pass the domain of an NTLM login to user space Jan Koester
2026-10-28 6:20 ` [RFC PATCH 4/6] ksmbd: map user and group ids to Windows SIDs through " Jan Koester
2026-10-28 6:20 ` [RFC PATCH 5/6] ksmbd: show classic users as S-1-22-1-<uid> on every server Jan Koester
2026-10-28 6:20 ` [RFC PATCH 6/6] Documentation: uid_r: describe ksmbd's SID mapping Jan Koester
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox