smb: client: validate the whole DACL before rewriting it in cifsacl

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: bc3e9dd9d1 ("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>
This commit is contained in:
Michael Bommarito 2026-04-19 20:11:31 -04:00 committed by Steve French
parent a58c5af19f
commit 0a8cf16556

View 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);