]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
smb: client: validate the whole DACL before rewriting it in cifsacl
authorMichael Bommarito <michael.bommarito@gmail.com>
Mon, 20 Apr 2026 00:11:31 +0000 (20:11 -0400)
committerSteve French <stfrench@microsoft.com>
Wed, 22 Apr 2026 14:54:07 +0000 (09:54 -0500)
build_sec_desc() and id_mode_to_cifs_acl() derive a DACL pointer from a
server-supplied dacloffset and then use the incoming ACL to rebuild the
chmod/chown security descriptor.

The original fix only checked that the struct smb_acl header fits before
reading dacl_ptr->size or dacl_ptr->num_aces.  That avoids the immediate
header-field OOB read, but the rewrite helpers still walk ACEs based on
pdacl->num_aces with no structural validation of the incoming DACL body.

A malicious server can return a truncated DACL that still contains a
header, claims one or more ACEs, and then drive
replace_sids_and_copy_aces() or set_chmod_dacl() past the validated
extent while they compare or copy attacker-controlled ACEs.

Factor the DACL structural checks into validate_dacl(), extend them to
validate each ACE against the DACL bounds, and use the shared validator
before the chmod/chown rebuild paths.  parse_dacl() reuses the same
validator so the read-side parser and write-side rewrite paths agree on
what constitutes a well-formed incoming DACL.

Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Assisted-by: Codex:gpt-5-4
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsacl.c

index c920039d733c3b80e81b762ddf0ee82aa6087ecc..cb4060ba5e318d57c92173fa2f36b48534ecf60e 100644 (file)
@@ -758,6 +758,77 @@ static void dump_ace(struct smb_ace *pace, char *end_of_acl)
 }
 #endif
 
+static int validate_dacl(struct smb_acl *pdacl, char *end_of_acl)
+{
+       int i, ace_hdr_size, ace_size, min_ace_size;
+       u16 dacl_size, num_aces;
+       char *acl_base, *end_of_dacl;
+       struct smb_ace *pace;
+
+       if (!pdacl)
+               return 0;
+
+       if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl)) {
+               cifs_dbg(VFS, "ACL too small to parse DACL\n");
+               return -EINVAL;
+       }
+
+       dacl_size = le16_to_cpu(pdacl->size);
+       if (dacl_size < sizeof(struct smb_acl) ||
+           end_of_acl < (char *)pdacl + dacl_size) {
+               cifs_dbg(VFS, "ACL too small to parse DACL\n");
+               return -EINVAL;
+       }
+
+       num_aces = le16_to_cpu(pdacl->num_aces);
+       if (!num_aces)
+               return 0;
+
+       ace_hdr_size = offsetof(struct smb_ace, sid) +
+               offsetof(struct smb_sid, sub_auth);
+       min_ace_size = ace_hdr_size + sizeof(__le32);
+       if (num_aces > (dacl_size - sizeof(struct smb_acl)) / min_ace_size) {
+               cifs_dbg(VFS, "ACL too small to parse DACL\n");
+               return -EINVAL;
+       }
+
+       end_of_dacl = (char *)pdacl + dacl_size;
+       acl_base = (char *)pdacl;
+       ace_size = sizeof(struct smb_acl);
+
+       for (i = 0; i < num_aces; ++i) {
+               if (end_of_dacl - acl_base < ace_size) {
+                       cifs_dbg(VFS, "ACL too small to parse ACE\n");
+                       return -EINVAL;
+               }
+
+               pace = (struct smb_ace *)(acl_base + ace_size);
+               acl_base = (char *)pace;
+
+               if (end_of_dacl - acl_base < ace_hdr_size ||
+                   pace->sid.num_subauth == 0 ||
+                   pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) {
+                       cifs_dbg(VFS, "ACL too small to parse ACE\n");
+                       return -EINVAL;
+               }
+
+               ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth;
+               if (end_of_dacl - acl_base < ace_size ||
+                   le16_to_cpu(pace->size) < ace_size) {
+                       cifs_dbg(VFS, "ACL too small to parse ACE\n");
+                       return -EINVAL;
+               }
+
+               ace_size = le16_to_cpu(pace->size);
+               if (end_of_dacl - acl_base < ace_size) {
+                       cifs_dbg(VFS, "ACL too small to parse ACE\n");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
                       struct smb_sid *pownersid, struct smb_sid *pgrpsid,
                       struct cifs_fattr *fattr, bool mode_from_special_sid)
@@ -765,7 +836,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
        int i;
        u16 num_aces = 0;
        int acl_size;
-       char *acl_base;
+       char *acl_base, *end_of_dacl;
        struct smb_ace **ppace;
 
        /* BB need to add parm so we can store the SID BB */
@@ -777,12 +848,8 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
                return;
        }
 
-       /* validate that we do not go past end of acl */
-       if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) ||
-           end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) {
-               cifs_dbg(VFS, "ACL too small to parse DACL\n");
+       if (validate_dacl(pdacl, end_of_acl))
                return;
-       }
 
        cifs_dbg(NOISY, "DACL revision %d size %d num aces %d\n",
                 le16_to_cpu(pdacl->revision), le16_to_cpu(pdacl->size),
@@ -793,6 +860,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
           user/group/other have no permissions */
        fattr->cf_mode &= ~(0777);
 
+       end_of_dacl = (char *)pdacl + le16_to_cpu(pdacl->size);
        acl_base = (char *)pdacl;
        acl_size = sizeof(struct smb_acl);
 
@@ -800,35 +868,15 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
        if (num_aces > 0) {
                umode_t denied_mode = 0;
 
-               if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
-                               (offsetof(struct smb_ace, sid) +
-                                offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
-                       return;
-
                ppace = kmalloc_objs(struct smb_ace *, num_aces);
                if (!ppace)
                        return;
 
                for (i = 0; i < num_aces; ++i) {
-                       if (end_of_acl - acl_base < acl_size)
-                               break;
-
                        ppace[i] = (struct smb_ace *) (acl_base + acl_size);
-                       acl_base = (char *)ppace[i];
-                       acl_size = offsetof(struct smb_ace, sid) +
-                               offsetof(struct smb_sid, sub_auth);
-
-                       if (end_of_acl - acl_base < acl_size ||
-                           ppace[i]->sid.num_subauth == 0 ||
-                           ppace[i]->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
-                           (end_of_acl - acl_base <
-                            acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) ||
-                           (le16_to_cpu(ppace[i]->size) <
-                            acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth))
-                               break;
 
 #ifdef CONFIG_CIFS_DEBUG2
-                       dump_ace(ppace[i], end_of_acl);
+                       dump_ace(ppace[i], end_of_dacl);
 #endif
                        if (mode_from_special_sid &&
                            (compare_sids(&(ppace[i]->sid),
@@ -870,6 +918,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
                                (void *)ppace[i],
                                sizeof(struct smb_ace)); */
 
+                       acl_base = (char *)ppace[i];
                        acl_size = le16_to_cpu(ppace[i]->size);
                }
 
@@ -1293,10 +1342,9 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
        dacloffset = le32_to_cpu(pntsd->dacloffset);
        if (dacloffset) {
                dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
-               if (end_of_acl < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) {
-                       cifs_dbg(VFS, "Server returned illegal ACL size\n");
-                       return -EINVAL;
-               }
+               rc = validate_dacl(dacl_ptr, end_of_acl);
+               if (rc)
+                       return rc;
        }
 
        owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
@@ -1662,6 +1710,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
                dacloffset = le32_to_cpu(pntsd->dacloffset);
                if (dacloffset) {
                        dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
+                       rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
+                       if (rc) {
+                               kfree(pntsd);
+                               cifs_put_tlink(tlink);
+                               return rc;
+                       }
                        if (mode_from_sid)
                                nsecdesclen +=
                                        le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace);