From: Ian Bridges <icb@fastmail.org>
To: linkinjeon@kernel.org
Cc: senozhatsky@chromium.org, tom@talpey.com,
chenxiaosong@chenxiaosong.com, linux-cifs@vger.kernel.org,
linux-kernel@vger.kernel.org,
Aamir Ahmed <elb12345@hotmail.co.uk>,
Ian Bridges <icb@fastmail.org>,
stable@vger.kernel.org
Subject: [PATCH v4] ksmbd: grant a caller what its groups and OWNER RIGHTS ACEs allow
Date: Wed, 4 Nov 2026 12:58:04 -0600 [thread overview]
Message-ID: <20261104185804.195058-1-icb@fastmail.org> (raw)
smb_check_perm_dacl() does not match ACEs for the caller's Unix
groups and refuses an owner named only by OWNER RIGHTS. These callers
cannot use rights the DACL grants them under MS-DTYP 2.5.3.2.
Match Unix group SIDs (S-1-22-2) against the caller's credentials. Use
the ordered DACL result when the existing SID lookup and POSIX ACL
fallback do not decide access. In that result, deny ACEs for the
caller's groups limit what it grants, also for MAXIMUM_ALLOWED. Count
only the exact OWNER RIGHTS SID in that result, since compare_sids()
also matches shorter and longer SIDs, and return only file access
rights. Preserve the refusal for non-inherit-only object deny ACEs,
which ksmbd does not parse.
MAXIMUM_ALLOWED still excludes group allow ACEs. Including them
requires corresponding changes in smb2_open() to avoid breaking
existing opens. An open relying on group grants can therefore still
fail when it requests a maximal access reply. For the owner, that
reply leaves out the rights of its groups.
Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Cc: stable@vger.kernel.org
Signed-off-by: Ian Bridges <icb@fastmail.org>
---
Notes:
Since v3
- Remove the redundant braces around the ordered DACL walk (Namjae
Jeon [3]).
- Handle MAXIMUM_ALLOWED after the walk in one branch, drop the
redundant in_group flag, use maximal throughout and rename the
group masks.
- Note that compare_sids() also matches shorter SIDs, and reword the
comments.
Since v2
- Honor deny ACEs for the caller's groups in the new MAXIMUM_ALLOWED
fallback (Namjae Jeon [2]).
- Count only the exact OWNER RIGHTS SID in the ordered result and
return only file access rights.
- Keep the existing refusal for non-inherit-only object deny ACEs.
Since v1
- Clear access_bits for explicit requests, preserving refusals for
ACE types the existing lookup does not handle (Sashiko [1]).
The hide unreadable listing and the app instance check use the same
answer and change with it.
Tested on ksmbd-for-next e49296d51ccb with KASAN, with no reports or
warnings. A reproducer is available on request.
v3: https://lore.kernel.org/linux-cifs/20261103150458.11591-1-icb@fastmail.org/
v2: https://lore.kernel.org/linux-cifs/20261102200353.884509-1-icb@fastmail.org/
v1: https://lore.kernel.org/linux-cifs/20261102061537.830714-3-icb@fastmail.org/
[1] https://sashiko.dev/#/patchset/20261102061537.830714-1-icb%40fastmail.org
[2] https://lore.kernel.org/linux-cifs/CAKYAXd-s-g2V4LNY8e5SsH=+0r3qLY+AeOa5Lp9K0T5p_tnA8A@mail.gmail.com/
[3] https://lore.kernel.org/linux-cifs/CAKYAXd9xNLn1Nk1QzD_Kep1TcgJmGNsT+UTg6Ed0DjjmRRW4+Q@mail.gmail.com/
This fix was developed with LLM assistance.
fs/smb/server/smbacl.c | 162 +++++++++++++++++++++++++++++------------
1 file changed, 117 insertions(+), 45 deletions(-)
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index ab76c5c9e0ea..32feafe9f73b 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1459,6 +1459,35 @@ bool smb_inherit_flags(int flags, bool is_dir)
return false;
}
+/* Whether the caller's credentials hold the group of an S-1-22-2-<gid> SID */
+static bool smb_sid_in_group(const struct smb_sid *sid)
+{
+ gid_t gid;
+
+ if (sid->num_subauth != sid_unix_groups.num_subauth + 1 ||
+ compare_sids(sid, &sid_unix_groups))
+ return false;
+ gid = le32_to_cpu(sid->sub_auth[sid->num_subauth - 1]);
+ /*
+ * ksmbd_override_fsids() builds the caller's credentials with
+ * make_kgid(&init_user_ns, ...), so the group membership test must
+ * compare in that space. Applying the mount idmap here (as when
+ * resolving a SID to an on-disk gid) would mismatch on idmapped
+ * mounts.
+ */
+ return in_group_p(make_kgid(&init_user_ns, gid));
+}
+
+/*
+ * compare_sids() compares only the sub-authorities both SIDs have, so it
+ * also matches a shorter or longer SID, such as S-1-3 or S-1-3-4-777
+ */
+static bool smb_sid_is_owner_rights(const struct smb_sid *sid)
+{
+ return sid->num_subauth == sid_owner_rights.num_subauth &&
+ !compare_sids(sid, &sid_owner_rights);
+}
+
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
__le32 *pdaccess, __le32 raw_daccess, int uid,
bool strict)
@@ -1475,13 +1504,15 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
int granted = requested;
struct smb_ace *ace;
int i, found = 0;
- unsigned int access_bits = 0, denied = 0;
+ unsigned int access_bits = 0, denied = 0, grp_granted = 0;
+ unsigned int grp_allowed = 0, grp_denied = 0, bits;
struct smb_ace *others_ace = NULL;
- bool or_ace = false;
+ bool or_ace = false, obj_deny = false, member, counted;
struct posix_acl_entry *pa_entry;
unsigned int sid_type = SIDOWNER;
unsigned short ace_size;
bool is_owner, owner_rights = false;
+ bool maximal = *pdaccess & FILE_MAXIMAL_ACCESS_LE;
vfsuid_t vfsuid;
ksmbd_debug(SMB, "check permission using windows acl\n");
@@ -1522,7 +1553,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
*/
if (is_owner)
allowed = READ_CONTROL | WRITE_DAC;
- if (*pdaccess & FILE_MAXIMAL_ACCESS_LE)
+ if (maximal)
requested |= allowed;
if (!allowed || requested & ~allowed)
rc = -EACCES;
@@ -1531,53 +1562,83 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
goto err_out;
}
- if (*pdaccess & FILE_MAXIMAL_ACCESS_LE) {
- ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
- aces_size = pdacl_size - sizeof(struct smb_acl);
- for (i = 0; i < le16_to_cpu(pdacl->num_aces); i++) {
- if (aces_size < offsetof(struct smb_ace, sid) +
- CIFS_SID_BASE_SIZE)
- break;
- ace_size = le16_to_cpu(ace->size);
- if (ace_size > aces_size ||
- ace_size < offsetof(struct smb_ace, sid) +
- CIFS_SID_BASE_SIZE)
- break;
- aces_size -= ace_size;
+ /*
+ * Evaluate the DACL in order (MS-DTYP 2.5.3.2). access_bits collects
+ * the MAXIMUM_ALLOWED result. grp_granted collects what ACEs for the
+ * caller's groups, or OWNER RIGHTS for the owner, grant. It is used
+ * when the SID lookup and POSIX ACL fallback below do not decide.
+ */
+ ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
+ aces_size = pdacl_size - sizeof(struct smb_acl);
+ for (i = 0; i < le16_to_cpu(pdacl->num_aces); i++) {
+ if (aces_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
+ break;
+ ace_size = le16_to_cpu(ace->size);
+ if (ace_size > aces_size ||
+ ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE)
+ break;
+ aces_size -= ace_size;
- if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
- ace_size < offsetof(struct smb_ace, sid) +
- CIFS_SID_BASE_SIZE +
- sizeof(__le32) * ace->sid.num_subauth)
- break;
+ if (ace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
+ ace_size < offsetof(struct smb_ace, sid) +
+ CIFS_SID_BASE_SIZE +
+ sizeof(__le32) * ace->sid.num_subauth)
+ break;
- if (!compare_sids(&sid_owner_rights, &ace->sid)) {
- owner_rights = true;
- if (!is_owner)
- goto next_ace;
- }
+ if ((ace->type == ACCESS_DENIED_OBJECT_ACE_TYPE ||
+ ace->type == ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE) &&
+ !(ace->flags & INHERIT_ONLY_ACE))
+ obj_deny = true;
- if (ace->flags & INHERIT_ONLY_ACE ||
- (compare_sids(&sid, &ace->sid) &&
- compare_sids(&sid_unix_NFS_mode, &ace->sid) &&
- compare_sids(&sid_everyone, &ace->sid) &&
- compare_sids(&sid_authusers, &ace->sid) &&
- compare_sids(&sid_owner_rights, &ace->sid)))
+ if (!compare_sids(&sid_owner_rights, &ace->sid)) {
+ owner_rights = true;
+ if (!is_owner)
goto next_ace;
+ }
- switch (ace->type) {
- case ACCESS_ALLOWED_ACE_TYPE:
- access_bits |= le32_to_cpu(ace->access_req);
- break;
- case ACCESS_DENIED_ACE_TYPE:
- case ACCESS_DENIED_CALLBACK_ACE_TYPE:
- denied |= ~access_bits &
- le32_to_cpu(ace->access_req);
+ member = smb_sid_in_group(&ace->sid);
+ /* The grp_ masks count groups and OWNER RIGHTS only */
+ counted = member ||
+ (is_owner && smb_sid_is_owner_rights(&ace->sid));
+ if (ace->flags & INHERIT_ONLY_ACE ||
+ (compare_sids(&sid, &ace->sid) &&
+ compare_sids(&sid_unix_NFS_mode, &ace->sid) &&
+ compare_sids(&sid_everyone, &ace->sid) &&
+ compare_sids(&sid_authusers, &ace->sid) &&
+ compare_sids(&sid_owner_rights, &ace->sid) &&
+ !member))
+ goto next_ace;
+
+ bits = le32_to_cpu(ace->access_req);
+ switch (ace->type) {
+ case ACCESS_ALLOWED_ACE_TYPE:
+ if (counted)
+ grp_allowed |= bits;
+ /* MAXIMUM_ALLOWED takes no group grants */
+ if (maximal && member)
break;
- }
-next_ace:
- ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
+ if (counted)
+ grp_granted |= bits;
+ access_bits |= bits;
+ break;
+ case ACCESS_DENIED_ACE_TYPE:
+ case ACCESS_DENIED_CALLBACK_ACE_TYPE:
+ if (counted)
+ grp_denied |= ~grp_allowed & bits;
+ /* access_bits takes no group denials either */
+ if (!member)
+ denied |= ~access_bits & bits;
+ break;
}
+next_ace:
+ ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
+ }
+
+ /* Keep only file rights, not MAXIMUM_ALLOWED or generic bits */
+ grp_granted &= GENERIC_ALL_FLAGS & ~grp_denied;
+ if (maximal) {
if (is_owner && !owner_rights)
access_bits |= READ_CONTROL | WRITE_DAC |
FILE_READ_ATTRIBUTES | DELETE;
@@ -1587,6 +1648,9 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
(access_bits & GENERIC_READ_FLAGS) == GENERIC_READ_FLAGS)
access_bits |= FILE_EXECUTE;
granted = requested | access_bits;
+ } else {
+ /* An explicit request uses only grp_granted and obj_deny */
+ access_bits = 0;
}
ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
@@ -1668,7 +1732,15 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
*/
if (is_owner && !or_ace)
allowed = READ_CONTROL | WRITE_DAC;
- if (*pdaccess & FILE_MAXIMAL_ACCESS_LE)
+ /*
+ * Add what ACEs for the caller's groups, or OWNER
+ * RIGHTS for the owner, grant in DACL order, unless an
+ * object deny ACE, which ksmbd does not parse, could
+ * apply.
+ */
+ if (!obj_deny)
+ allowed |= grp_granted;
+ if (maximal)
requested |= allowed;
if (!allowed || requested & ~allowed) {
ksmbd_debug(SMB, "Can't find corresponding sid\n");
@@ -1680,7 +1752,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
}
}
- if (!(*pdaccess & FILE_MAXIMAL_ACCESS_LE)) {
+ if (!maximal) {
switch (ace->type) {
case ACCESS_ALLOWED_ACE_TYPE:
access_bits = le32_to_cpu(ace->access_req);
base-commit: e49296d51ccbf2ad0ed0484db1382f1d7217bd6e
--
2.47.3
next reply other threads:[~2026-11-04 18:58 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 18:58 Ian Bridges [this message]
2026-11-04 22:43 ` Namjae Jeon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104185804.195058-1-icb@fastmail.org \
--to=icb@fastmail.org \
--cc=chenxiaosong@chenxiaosong.com \
--cc=elb12345@hotmail.co.uk \
--cc=linkinjeon@kernel.org \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=senozhatsky@chromium.org \
--cc=stable@vger.kernel.org \
--cc=tom@talpey.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®