[PATCH] smb: client: validate dacloffset before building DACL pointers

Michael Bommarito posted 1 patch 1 month, 3 weeks ago
fs/smb/client/cifsacl.c | 35 ++++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
[PATCH] smb: client: validate dacloffset before building DACL pointers
Posted by Michael Bommarito 1 month, 3 weeks ago
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
Re: [PATCH] smb: client: validate dacloffset before building DACL pointers
Posted by Steve French 1 month, 2 weeks ago
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