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 v3 1/2] ksmbd: evaluate the DACL in order for explicit access requests
Date: Sun, 8 Nov 2026 12:20:10 -0600 [thread overview]
Message-ID: <20261108182011.1888520-2-icb@fastmail.org> (raw)
In-Reply-To: <20261108182011.1888520-1-icb@fastmail.org>
For requests without MAXIMUM_ALLOWED, smb_check_perm_dacl() selects one
ACE instead of evaluating the DACL in order. When the selected ACE is a
deny ACE, the function grants the complement of its mask, and deny ACEs
before it are ignored. The selected ACE can also be inherit-only,
unsupported, or an NFS mode ACE.
Guest and anonymous sessions also match Authenticated Users. These bugs
can grant read or write access that the stored DACL does not grant. For
example, a deny of FILE_READ_DATA grants FILE_WRITE_DATA, and guests can
read files granted only to Authenticated Users.
Evaluate explicit requests in DACL order. Use exact SID matching for the
caller, Everyone, Authenticated Users, OWNER RIGHTS and the caller's
groups. Match Authenticated Users only for authenticated sessions, and
ignore the NFS mode SID.
An Authenticated Users ACE must still prevent the POSIX ACL fallback
from replacing the DACL result for guest and anonymous sessions. The ACE
does not grant them access.
Keep the existing single-ACE behavior when a non-inherit-only object
deny ACE is present because ksmbd does not parse those ACEs. Guest and
anonymous sessions do not match Authenticated Users there either.
MAXIMUM_ALLOWED behavior is 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.
inode_permission() continues to enforce that mode.
DACL-order evaluation keeps a right granted by an allow ACE before a
later deny ACE.
fs/smb/server/mgmt/user_config.h | 5 +++
fs/smb/server/smb2pdu.c | 11 ++++--
fs/smb/server/smbacl.c | 58 +++++++++++++++++++++++-------
fs/smb/server/smbacl.h | 2 +-
fs/smb/server/tests/smbacl_kunit.c | 4 +--
5 files changed, 62 insertions(+), 18 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..50a03101cd8a 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;
@@ -1507,12 +1513,17 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
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, obj_deny = false, member, counted;
+ bool or_ace = false, obj_deny = false, au_ace = 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;
+ /*
+ * 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,15 +1706,23 @@ 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;
+ if (!compare_sids(&sid_authusers, &ace->sid))
+ au_ace = true;
ace = (struct smb_ace *)((char *)ace + le16_to_cpu(ace->size));
}
if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
posix_acls = get_inode_acl(d_inode(path->dentry), ACL_TYPE_ACCESS);
- if (!IS_ERR_OR_NULL(posix_acls) && !found && !others_ace) {
+ /*
+ * An Authenticated Users ACE keeps the POSIX ACL from deciding,
+ * also for sessions that it does not match.
+ */
+ if (!IS_ERR_OR_NULL(posix_acls) && !found && !others_ace &&
+ !au_ace) {
unsigned int id = -1;
pa_entry = posix_acls->a_entries;
@@ -1752,7 +1786,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-08 18:20 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-08 18:20 [PATCH v3 0/2] ksmbd: fix explicit DACL checks and parent-derived rights Ian Bridges
2026-11-08 18:20 ` Ian Bridges [this message]
2026-11-08 18:20 ` [PATCH v3 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=20261108182011.1888520-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®