mirror of
https://github.com/torvalds/linux.git
synced 2026-05-26 16:12:59 +02:00
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:
parent
a58c5af19f
commit
0a8cf16556
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user