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, Ian Bridges <icb@fastmail.org>,
stable@vger.kernel.org
Subject: [PATCH v2 1/2] ksmbd: evaluate the DACL in order for explicit access requests
Date: Sat, 7 Nov 2026 17:19:40 -0600 [thread overview]
Message-ID: <20261107231941.1824289-2-icb@fastmail.org> (raw)
In-Reply-To: <20261107231941.1824289-1-icb@fastmail.org>
For requests without MAXIMUM_ALLOWED, smb_check_perm_dacl() selects one
ACE: the first for the caller or the last Everyone or Authenticated
Users ACE. When that ACE is a deny ACE, the function allows the
complement of the ACE's mask, so the deny grants every right it does
not name. Deny ACEs before the selected ACE are ignored. The selected
ACE can also be an inherit-only ACE, an ACE of an unsupported type, or
an NFS mode ACE. Guest and anonymous sessions also match Authenticated
Users, although they are not authenticated.
These bugs give real SMB access on acl_xattr shares. A deny of
FILE_READ_DATA can grant FILE_WRITE_DATA, and a deny of FILE_WRITE_DATA
can grant FILE_READ_DATA. A deny ACE for Everyone does the same for
every caller, including guests. Guest and anonymous sessions can also
read files granted only to Authenticated Users.
Use the ordered DACL walk for explicit requests. Match, by exact SID,
the caller, Everyone, Authenticated Users for authenticated sessions,
OWNER RIGHTS for the owner and the caller's groups. Ignore the NFS
mode SID.
Keep the single-ACE evaluation when a non-inherit-only object deny ACE
is present, as ksmbd does not parse those ACEs. Guest and anonymous
sessions do not match Authenticated Users there either.
MAXIMUM_ALLOWED remains unchanged.
Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Fixes: cc2f133e80eb ("ksmbd: fix maximum allowed access checks")
Cc: stable@vger.kernel.org
Signed-off-by: Ian Bridges <icb@fastmail.org>
---
Notes:
This patch changes explicit requests only. MAXIMUM_ALLOWED and
maximal-access replies still treat guest and anonymous sessions as
Authenticated Users.
The NFS mode ACE carries the POSIX mode but names no caller, so the
DACL check ignores it. inode_permission() still enforces the mode.
Ordered evaluation now allows a right that an allow ACE grants
before a later deny ACE names it. A reproducer is available on
request.
fs/smb/server/mgmt/user_config.h | 5 ++++
fs/smb/server/smb2pdu.c | 11 +++++--
fs/smb/server/smbacl.c | 47 +++++++++++++++++++++++-------
fs/smb/server/smbacl.h | 2 +-
fs/smb/server/tests/smbacl_kunit.c | 4 +--
5 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h
index cc460b4ff7d3..e116b2ee9917 100644
--- a/fs/smb/server/mgmt/user_config.h
+++ b/fs/smb/server/mgmt/user_config.h
@@ -67,4 +67,9 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
void ksmbd_free_user(struct ksmbd_user *user);
bool ksmbd_anonymous_user(struct ksmbd_user *user);
bool ksmbd_compare_user(struct ksmbd_user *u1, struct ksmbd_user *u2);
+
+static inline bool ksmbd_user_authenticated(struct ksmbd_user *user)
+{
+ return user && !user_guest(user) && !ksmbd_anonymous_user(user);
+}
#endif /* __USER_CONFIG_MANAGEMENT_H__ */
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 5bbc33d5a3a8..72981756ee23 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4338,7 +4338,8 @@ static int smb2_handle_app_instance_id(struct ksmbd_work *work,
/* Authorize the referenced open even if its name changes meanwhile. */
old_path = &old_fp->filp->f_path;
rc = smb_check_perm_dacl(conn, old_path, &maximal_access, 0,
- work->sess->user->uid, false);
+ work->sess->user->uid,
+ ksmbd_user_authenticated(work->sess->user), false);
if (rc) {
/* Access denial prevents takeover, not the CREATE itself. */
ksmbd_put_durable_fd(old_fp);
@@ -4860,14 +4861,17 @@ 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->uid, false);
+ sess->user->uid,
+ ksmbd_user_authenticated(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->uid, false);
+ 0, sess->user->uid,
+ ksmbd_user_authenticated(sess->user),
+ false);
if (rc)
goto err_out;
@@ -6347,6 +6351,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
rc = smb_check_perm_dacl(priv->work->conn, &path,
&daccess, daccess,
priv->work->sess->user->uid,
+ ksmbd_user_authenticated(priv->work->sess->user),
true);
if (rc) {
dput(dent);
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 32feafe9f73b..f773ab8efb3f 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1478,6 +1478,12 @@ static bool smb_sid_in_group(const struct smb_sid *sid)
return in_group_p(make_kgid(&init_user_ns, gid));
}
+/* Whether two SIDs are the same SID */
+static bool smb_sid_equal(const struct smb_sid *a, const struct smb_sid *b)
+{
+ return a->num_subauth == b->num_subauth && !compare_sids(a, b);
+}
+
/*
* 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
@@ -1490,7 +1496,7 @@ static bool smb_sid_is_owner_rights(const struct smb_sid *sid)
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
__le32 *pdaccess, __le32 raw_daccess, int uid,
- bool strict)
+ bool authenticated, bool strict)
{
const struct mnt_idmap *idmap = mnt_idmap(path->mnt);
struct smb_ntsd *pntsd = NULL;
@@ -1513,6 +1519,11 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
unsigned short ace_size;
bool is_owner, owner_rights = false;
bool maximal = *pdaccess & FILE_MAXIMAL_ACCESS_LE;
+ /*
+ * MAXIMUM_ALLOWED requests match every session against
+ * Authenticated Users.
+ */
+ bool authusers = authenticated || maximal;
vfsuid_t vfsuid;
ksmbd_debug(SMB, "check permission using windows acl\n");
@@ -1564,9 +1575,10 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
/*
* 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.
+ * the result for the ACEs that apply to the caller, without its groups
+ * for MAXIMUM_ALLOWED. 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);
@@ -1606,10 +1618,18 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
(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) &&
+ (!authusers ||
+ compare_sids(&sid_authusers, &ace->sid)) &&
compare_sids(&sid_owner_rights, &ace->sid) &&
!member))
goto next_ace;
+ /* An explicit request counts only SIDs that match exactly */
+ if (!maximal && !member &&
+ !smb_sid_equal(&sid, &ace->sid) &&
+ !smb_sid_equal(&sid_everyone, &ace->sid) &&
+ !(authusers && smb_sid_equal(&sid_authusers, &ace->sid)) &&
+ !smb_sid_is_owner_rights(&ace->sid))
+ goto next_ace;
bits = le32_to_cpu(ace->access_req);
switch (ace->type) {
@@ -1627,8 +1647,8 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
case ACCESS_DENIED_CALLBACK_ACE_TYPE:
if (counted)
grp_denied |= ~grp_allowed & bits;
- /* access_bits takes no group denials either */
- if (!member)
+ /* MAXIMUM_ALLOWED takes no group denials either */
+ if (!(maximal && member))
denied |= ~access_bits & bits;
break;
}
@@ -1648,8 +1668,14 @@ 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 if (!obj_deny) {
+ /* An explicit request takes the ordered result */
+ access_bits &= ~denied;
} else {
- /* An explicit request uses only grp_granted and obj_deny */
+ /*
+ * ksmbd does not parse object deny ACEs, so with one present an
+ * explicit request keeps the answer of the ACE found below
+ */
access_bits = 0;
}
@@ -1680,7 +1706,8 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
!(ace->flags & INHERIT_ONLY_ACE))
or_ace = true;
if (!compare_sids(&sid_everyone, &ace->sid) ||
- !compare_sids(&sid_authusers, &ace->sid))
+ (authusers &&
+ !compare_sids(&sid_authusers, &ace->sid)))
others_ace = ace;
ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
@@ -1752,7 +1779,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
}
}
- if (!maximal) {
+ if (!maximal && obj_deny) {
switch (ace->type) {
case ACCESS_ALLOWED_ACE_TYPE:
access_bits = le32_to_cpu(ace->access_req);
diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h
index 3342cebab333..bbd35948a379 100644
--- a/fs/smb/server/smbacl.h
+++ b/fs/smb/server/smbacl.h
@@ -97,7 +97,7 @@ int smb_inherit_dacl(struct ksmbd_conn *conn, const struct path *path,
unsigned int uid, unsigned int gid);
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
__le32 *pdaccess, __le32 raw_daccess, int uid,
- bool strict);
+ bool authenticated, 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 posix_open,
diff --git a/fs/smb/server/tests/smbacl_kunit.c b/fs/smb/server/tests/smbacl_kunit.c
index 33496b4d31a3..bf28439a7696 100644
--- a/fs/smb/server/tests/smbacl_kunit.c
+++ b/fs/smb/server/tests/smbacl_kunit.c
@@ -229,7 +229,7 @@ static void ksmbd_smb_check_perm_dacl_boundary_test(struct kunit *test)
goto out;
rc = smb_check_perm_dacl(NULL, &file->f_path, &daccess,
- cpu_to_le32(FILE_READ_DATA), 0, false);
+ cpu_to_le32(FILE_READ_DATA), 0, true, false);
/*
* The post-boundary ACE (ACE #2, beyond pdacl->size) grants
@@ -271,7 +271,7 @@ ksmbd_smb_check_perm_dacl_maximal_boundary_test(struct kunit *test)
goto out;
rc = smb_check_perm_dacl(NULL, &file->f_path, &daccess,
- FILE_MAXIMAL_ACCESS_LE, 0, false);
+ FILE_MAXIMAL_ACCESS_LE, 0, true, false);
KUNIT_EXPECT_EQ(test, 0, rc);
if (rc)
goto out;
--
2.47.3
next prev parent reply other threads:[~2026-11-07 23:19 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 23:19 [PATCH v2 0/2] ksmbd: fix explicit DACL checks and parent-derived rights Ian Bridges
2026-11-07 23:19 ` Ian Bridges [this message]
2026-11-07 23:19 ` [PATCH v2 2/2] ksmbd: let the parent's DACL grant DELETE and FILE_READ_ATTRIBUTES Ian Bridges
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=20261107231941.1824289-2-icb@fastmail.org \
--to=icb@fastmail.org \
--cc=chenxiaosong@chenxiaosong.com \
--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®