]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
smb: client: validate dacloffset before building DACL pointers
authorMichael Bommarito <michael.bommarito@gmail.com>
Mon, 20 Apr 2026 14:47:47 +0000 (10:47 -0400)
committerSteve French <stfrench@microsoft.com>
Thu, 7 May 2026 19:09:36 +0000 (14:09 -0500)
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>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsacl.c

index a2750f1e3d90bd5f394f441b4e401a49c33d37a5..786dbbc43c5b9991eb13074fbe03a27b4bc44ca1 100644 (file)
@@ -1264,6 +1264,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,
@@ -1284,7 +1295,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),
@@ -1315,11 +1325,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;
 }
@@ -1342,6 +1359,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)
@@ -1710,6 +1732,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) {
@@ -1752,6 +1780,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);