fs/smb/client/cifsacl.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-)
parse_sec_desc(), build_sec_desc(), and the chown path in
id_mode_to_cifs_acl() all add the server-supplied dacloffset to pntsd
before proving a DACL header fits inside the returned security
descriptor.
On 32-bit builds a malicious server can return dacloffset near
U32_MAX, wrap the derived DACL pointer below end_of_acl, and then slip
past the later pointer-based bounds checks. build_sec_desc() and
id_mode_to_cifs_acl() can then dereference DACL fields from the wrapped
pointer in the chmod/chown rewrite paths.
Validate dacloffset numerically before building any DACL pointer and
reuse the same helper at the three DACL entry points.
Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
This applies on top of
[PATCH v2] smb: client: validate the whole DACL before rewriting it
in cifsacl
https://lore.kernel.org/linux-cifs/20260420001131.2865776-1-michael.bommarito@gmail.com/
so that the new dacl_offset_valid() numeric precheck sits upstream of
that series' validate_dacl() structural check at all three call sites.
The two patches are independent fixes for different bug classes on the
same three entry points; applying this one without the KCIFS2 v2 patch
first will fail on the build_sec_desc() hunk because the trailing
context line "rc = validate_dacl(dacl_ptr, end_of_acl)" only exists
after v2. If you prefer a different ordering, happy to reroll on a
plain mainline base instead.
fs/smb/client/cifsacl.c | 35 ++++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index cb4060ba5e31..87d2a58fc8b4 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -1263,6 +1263,17 @@ static int parse_sid(struct smb_sid *psid, char *end_of_acl)
return 0;
}
+static bool dacl_offset_valid(unsigned int acl_len, __u32 dacloffset)
+{
+ if (acl_len < sizeof(struct smb_acl))
+ return false;
+
+ if (dacloffset < sizeof(struct smb_ntsd))
+ return false;
+
+ return dacloffset <= acl_len - sizeof(struct smb_acl);
+}
+
/* Convert CIFS ACL to POSIX form */
static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
@@ -1283,7 +1294,6 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
group_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->gsidoffset));
dacloffset = le32_to_cpu(pntsd->dacloffset);
- dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n",
pntsd->revision, pntsd->type, le32_to_cpu(pntsd->osidoffset),
le32_to_cpu(pntsd->gsidoffset),
@@ -1314,11 +1324,18 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
return rc;
}
- if (dacloffset)
+ if (dacloffset) {
+ if (!dacl_offset_valid(acl_len, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ return -EINVAL;
+ }
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr,
group_sid_ptr, fattr, get_mode_from_special_sid);
- else
+ } else {
cifs_dbg(FYI, "no ACL\n"); /* BB grant all or default perms? */
+ }
return rc;
}
@@ -1341,6 +1358,11 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ return -EINVAL;
+ }
+
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
rc = validate_dacl(dacl_ptr, end_of_acl);
if (rc)
@@ -1709,6 +1731,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2);
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ rc = -EINVAL;
+ goto id_mode_to_cifs_acl_exit;
+ }
+
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
if (rc) {
@@ -1751,6 +1779,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
rc = ops->set_acl(pnntsd, nsecdesclen, inode, path, aclflag);
cifs_dbg(NOISY, "set_cifs_acl rc: %d\n", rc);
}
+id_mode_to_cifs_acl_exit:
cifs_put_tlink(tlink);
kfree(pnntsd);
--
2.53.0
merged into cifs-2.6.git for-next pending additional testing and review
On Mon, Apr 20, 2026 at 10:44 AM Michael Bommarito
<michael.bommarito@gmail.com> wrote:
>
> parse_sec_desc(), build_sec_desc(), and the chown path in
> id_mode_to_cifs_acl() all add the server-supplied dacloffset to pntsd
> before proving a DACL header fits inside the returned security
> descriptor.
>
> On 32-bit builds a malicious server can return dacloffset near
> U32_MAX, wrap the derived DACL pointer below end_of_acl, and then slip
> past the later pointer-based bounds checks. build_sec_desc() and
> id_mode_to_cifs_acl() can then dereference DACL fields from the wrapped
> pointer in the chmod/chown rewrite paths.
>
> Validate dacloffset numerically before building any DACL pointer and
> reuse the same helper at the three DACL entry points.
>
> Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
> This applies on top of
>
> [PATCH v2] smb: client: validate the whole DACL before rewriting it
> in cifsacl
> https://lore.kernel.org/linux-cifs/20260420001131.2865776-1-michael.bommarito@gmail.com/
>
> so that the new dacl_offset_valid() numeric precheck sits upstream of
> that series' validate_dacl() structural check at all three call sites.
> The two patches are independent fixes for different bug classes on the
> same three entry points; applying this one without the KCIFS2 v2 patch
> first will fail on the build_sec_desc() hunk because the trailing
> context line "rc = validate_dacl(dacl_ptr, end_of_acl)" only exists
> after v2. If you prefer a different ordering, happy to reroll on a
> plain mainline base instead.
>
> fs/smb/client/cifsacl.c | 35 ++++++++++++++++++++++++++++++++---
> 1 file changed, 32 insertions(+), 3 deletions(-)
>
> diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
> index cb4060ba5e31..87d2a58fc8b4 100644
> --- a/fs/smb/client/cifsacl.c
> +++ b/fs/smb/client/cifsacl.c
> @@ -1263,6 +1263,17 @@ static int parse_sid(struct smb_sid *psid, char *end_of_acl)
> return 0;
> }
>
> +static bool dacl_offset_valid(unsigned int acl_len, __u32 dacloffset)
> +{
> + if (acl_len < sizeof(struct smb_acl))
> + return false;
> +
> + if (dacloffset < sizeof(struct smb_ntsd))
> + return false;
> +
> + return dacloffset <= acl_len - sizeof(struct smb_acl);
> +}
> +
>
> /* Convert CIFS ACL to POSIX form */
> static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
> @@ -1283,7 +1294,6 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
> group_sid_ptr = (struct smb_sid *)((char *)pntsd +
> le32_to_cpu(pntsd->gsidoffset));
> dacloffset = le32_to_cpu(pntsd->dacloffset);
> - dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
> cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n",
> pntsd->revision, pntsd->type, le32_to_cpu(pntsd->osidoffset),
> le32_to_cpu(pntsd->gsidoffset),
> @@ -1314,11 +1324,18 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
> return rc;
> }
>
> - if (dacloffset)
> + if (dacloffset) {
> + if (!dacl_offset_valid(acl_len, dacloffset)) {
> + cifs_dbg(VFS, "Server returned illegal DACL offset\n");
> + return -EINVAL;
> + }
> +
> + dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
> parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr,
> group_sid_ptr, fattr, get_mode_from_special_sid);
> - else
> + } else {
> cifs_dbg(FYI, "no ACL\n"); /* BB grant all or default perms? */
> + }
>
> return rc;
> }
> @@ -1341,6 +1358,11 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
>
> dacloffset = le32_to_cpu(pntsd->dacloffset);
> if (dacloffset) {
> + if (!dacl_offset_valid(secdesclen, dacloffset)) {
> + cifs_dbg(VFS, "Server returned illegal DACL offset\n");
> + return -EINVAL;
> + }
> +
> dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
> rc = validate_dacl(dacl_ptr, end_of_acl);
> if (rc)
> @@ -1709,6 +1731,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
> nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2);
> dacloffset = le32_to_cpu(pntsd->dacloffset);
> if (dacloffset) {
> + if (!dacl_offset_valid(secdesclen, dacloffset)) {
> + cifs_dbg(VFS, "Server returned illegal DACL offset\n");
> + rc = -EINVAL;
> + goto id_mode_to_cifs_acl_exit;
> + }
> +
> dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
> rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
> if (rc) {
> @@ -1751,6 +1779,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
> rc = ops->set_acl(pnntsd, nsecdesclen, inode, path, aclflag);
> cifs_dbg(NOISY, "set_cifs_acl rc: %d\n", rc);
> }
> +id_mode_to_cifs_acl_exit:
> cifs_put_tlink(tlink);
>
> kfree(pnntsd);
> --
> 2.53.0
>
>
--
Thanks,
Steve
© 2016 - 2026 Red Hat, Inc.