Commit 0a8cf165 authored by Michael Bommarito's avatar Michael Bommarito Committed by Steve French
Browse files

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: bc3e9dd9 ("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: default avatarMichael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent a58c5af1
Loading
Loading
Loading
Loading
+85 −31
Original line number Diff line number Diff line
@@ -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);