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

Steve French <[email protected]> Sun, 3 May 2026 10:24:11 -0500
Newsgroups gmane.linux.kernel.stable,gmane.linux.kernel.cifs,gmane.network.samba.internals,gmane.linux.kernel
Message-ID <CAH2r5mv5wV6xJfmTNx3dTbs1wTDyYpLPsdq53butKq1N_OvAoQ@mail.gmail.com>
merged into cifs-2.6.git for-next pending additional testing and review

On Mon, Apr 20, 2026 at 10:44 AM Michael Bommarito
<[email protected]> 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: [email protected]
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Michael Bommarito <[email protected]>
> ---
> This applies on top of
>
>   [PATCH v2] smb: client: validate the whole DACL before rewriting it
>   in cifsacl
>   https://lore.kernel.org/linux-cifs/[email protected]/
>
> 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