From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=173.212.254.19 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794301303; b=d9FWZpYPq8WreaVcFnfgdY8XSDtNRPD3sYnw1fHPc3p8KtoZX9BKoxXjk5bld2089zJo gvTJrHGWCZWedGsIhqEh3xnfPAbp9+bM3viH1VXt1dN0x6rP2Rvd+sONc4ImB9pDa1O4O WcJ4GT06vl+6q5lR6utJg/YHqNNKcmkPjDvtFU+B8QXmIoltpRURAYH8Frz67jDY7GwD2 hFsyhcA0mRF2T2mKgpBVcPjaWn4m84uDUl76qWdY2TPAZyf3EUwfQysevMBUZ9+8fUrTJ lIDVIGgDpjF1uHS+7TeuNDzqt1f0816OEAeBGYBs0m00ghKkAo9tkdtOLjFUgfV0wiA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794301303; h=DMARC-Filter:DKIM-Signature:X-Virus-Scanned:Received:From:To:Cc: Subject:Date:Message-ID:MIME-Version:Content-Type: Content-Transfer-Encoding; bh=Q0a05qifzU0G7Exh+mb/eUo5GkcpEkxM1kUdfXaLv24=; b=bosflxOo44k40vvMLJEBIZrDjBHs3HR66mrG2E0XxI6HF3OhfLMeRl9MbCPLzdS7KhMw nzVcBu9soMW0MuYLRMXAjja4IOVoYR4Xt+XHvXJrlXW7QxepqLCWqDuiEh8uZXmAu3oBj R9FZRE41QOzOd64/5r+zOpggax2gonBb+WseQg/4lBXK68z59Lysi4QGMflWeWK1MkIAB DF5sQ4lYA0v6ufPuK9jevBFRT8v3lIvG8YOdYRnRqaHxlkC18N4cm2VxSsDo7CBIJTaR/ N0Qc4tAWxDgCdrqukdcOcsPl+GbUpdQQhGXew21yeyWdMEncgwqUVpGk8SNdp8Oye7g== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=tuxist.de; spf=pass smtp.mailfrom=tuxist.de; dkim=pass header.d=tuxist.de header.i=@tuxist.de header.a=rsa-sha256 header.s=201705 header.b="HX+m/4sj"; arc=none smtp.remote-ip=173.212.254.19 Received: from proxyplus-primary (groupware.tuxist.de [173.212.254.19]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A9A87606A337; Tue, 10 Nov 2026 09:01:40 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org A9A87606A337 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=tuxist.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=tuxist.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=tuxist.de header.i=@tuxist.de header.a=rsa-sha256 header.s=201705 header.b=HX+m/4sj DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tuxist.de; s=201705; h=from:to:subject:date:message-id; bh=Q0a05qifzU0G7Exh+mb/eUo5GkcpEkxM1kUdfXaLv24=; b=HX+m/4sji7GGsOj60oWH+OvuE5phL0mSMy4KxUidBT2e9OsJ8BA4L/HBzo4tSzwga2jKoWQQMXYwhMZBzAHpNKW6cDQ9JyBrLbHo3q2uVhwfvi6LOnKJvFECYTU32AwsjTtYPQu9i/pgWkZCeYCsd9vBHcuXrpEEfmObdV2g8A1pNYyz0894iBOqaZwHP9LV6qccyJkmr8KBvEhHpFHlPYXlJ6bHvzNxlr5mQtLHQYZwilyIEZfyn/CqR5BuPSM3T2/2nIeDAGr7B7yOH/2lwZhMZ1m5KwjrFgifJrd/V5/rghg4uQhvUxRn28LNJyBZWJj0B5hBHLXRgN0G1NRVzQ== Authentication-Results: proxyplus-primary; auth=pass smtp.auth=713ee033-d4f8-4c9d-9e24-a891eb9c547d X-Virus-Scanned: proxyplus-clamav builtin Received: from groupi2 ([10.1.2.46]) by proxyplus-primary (ProxyPlus) with ESMTPSA; Tue, 10 Nov 2026 09:01:08 +0000 From: Jan Koester To: linkinjeon@kernel.org, smfrench@gmail.com Cc: senozhatsky@chromium.org, tom@talpey.com, chenxiaosong@chenxiaosong.com, brauner@kernel.org, linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, samba-technical@lists.samba.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 4/6] ksmbd: map user and group ids to Windows SIDs through user space Date: Tue, 10 Nov 2026 09:02:46 +0000 Message-ID: X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit 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 --- 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 +#include +#include +#include +#include +#include +#include + +#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 +#include + +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 #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 "); 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--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