FAILED: patch "[PATCH] smb: client: validate the whole DACL before rewriting it in" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 0a8cf165566ba55a39fd0f4de172119dd646d39a
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026042446-omen-saddling-ddde@gregkh' --subject-prefix 'PATCH 5.15.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 0a8cf165566ba55a39fd0f4de172119dd646d39a Mon Sep 17 00:00:00 2001
From: Michael Bommarito <[email protected]>
Date: Sun, 19 Apr 2026 20:11:31 -0400
Subject: [PATCH] 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.
fs/smb/client/cifsacl.c +85 -31
@@ -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 );
Sasha Levin
maintainerPATCH 1/1 smb: client: validate the whole DACL before rewriting it in cifsacl
From: Michael Bommarito <[email protected]>
[ Upstream commit 0a8cf165566ba55a39fd0f4de172119dd646d39a ]
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.
--- fs/cifs/cifsacl.c | 95 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 10 deletions(-)
fs/cifs/cifsacl.c +85 -10
@@ -753,6 +753,78 @@ static void dump_ace(struct cifs_ace *pace, char *end_of_acl) }#endif +static int validate_dacl (struct cifs_acl *pdacl,char *end_of_acl) +{ +int i, ace_hdr_size, ace_size, min_ace_size; +u16 dacl_size; +u32 num_aces; +char *acl_base, *end_of_dacl; +struct cifs_ace *pace; + +if (!pdacl) +return 0 ; + +if (end_of_acl < (char *)pdacl +sizeof (struct cifs_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 cifs_acl ) || + end_of_acl < (char *)pdacl + dacl_size) { +cifs_dbg (VFS ,"ACL too small to parse DACL\n" ); +return -EINVAL ; + } + + num_aces =le32_to_cpu (pdacl->num_aces); +if (!num_aces) +return 0 ; + + ace_hdr_size = offsetof(struct cifs_ace , sid) + + offsetof(struct cifs_sid , sub_auth); + min_ace_size = ace_hdr_size +sizeof (__le32); +if (num_aces > (dacl_size -sizeof (struct cifs_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 cifs_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 cifs_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 cifs_acl *pdacl,char *end_of_acl,struct cifs_sid *pownersid,struct cifs_sid *pgrpsid,struct cifs_fattr *fattr,bool mode_from_special_sid)
@@ -760,7 +832,7 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,int i;int num_aces =0 ;int acl_size; -char *acl_base; +char *acl_base, *end_of_dacl;struct cifs_ace **ppace;/* BB need to add parm so we can store the SID BB */
@@ -772,11 +844,8 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,return ; } -/* validate that we do not go past end of acl */ -if (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),
@@ -787,6 +856,7 @@ static void parse_dacl(struct cifs_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 cifs_acl );
@@ -804,7 +874,7 @@ static void parse_dacl(struct cifs_acl *pdacl, char *end_of_acl,for (i =0 ; i < num_aces; ++i) { ppace[i] = (struct cifs_ace *) (acl_base + acl_size);#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),
@@ -1262,10 +1332,9 @@ static int build_sec_desc(struct cifs_ntsd *pntsd, struct cifs_ntsd *pnntsd, dacloffset =le32_to_cpu (pntsd->dacloffset);if (dacloffset) { dacl_ptr = (struct cifs_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 cifs_sid *)((char *)pntsd +
@@ -1625,6 +1694,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 cifs_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 +=le32_to_cpu (dacl_ptr->num_aces) *sizeof (struct cifs_ace );
Signature · 1 line
2.53.0
PATCH 1/1 smb: client: validate the whole DACL before rewriting it in cifsacl
Hi,
I can not find this patch in queue-5.15 and queue-6.1 of https://git.kernel.org/pub/scm/linux/kernel/git/stable/stable-queue.git,
Do you know what happened to this patch for Linux 5.15 and Linux 6.1?
Thanks,
Jiping
Greg KH
maintainerRe: [PATCH 5.15.y] smb: client: validate the whole DACL before rewriting it in cifsacl
Quoted 4 lines · Jiping Ma
Hi,
I can not find this patch in queue-5.15 and queue-6.1 of https://git.kernel.org/pub/scm/linux/kernel/git/stable/stable-queue.git,
Do you know what happened to this patch for Linux 5.15 and Linux 6.1?
What is the git id of the commit you are looking for?
thanks,
greg k-h
Re: [PATCH 5.15.y] smb: client: validate the whole DACL before rewriting it in cifsacl
In reply to Jiping MaQueued for 6.18.y, thanks.
Signature · 2 lines
Thanks, Sasha