Linux CIFS filesystem development
 help / color / mirror / Atom feed
* [PATCH] smb: client: fix heap overflow in cifs_do_set_acl() from mismatched ACL format sizes
@ 2026-08-25 18:32 Frank Sorenson
  2026-08-25 21:43 ` [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl() Frank Sorenson
  0 siblings, 1 reply; 7+ messages in thread
From: Frank Sorenson @ 2026-08-25 18:32 UTC (permalink / raw)
  To: linux-cifs; +Cc: pc, linkinjeon, stable

cifs_set_acl() validates ACL size using posix_acl_xattr_size(), which
computes the Linux xattr format size:

        4 + (count * 8)  // 4-byte header + 8 bytes per ACE

But cifs_do_set_acl() calls posix_acl_to_cifs() to write the CIFS
wire format into the same buffer:

        6 + (count * 10)  // 6-byte header + 10 bytes per ACE

For the same entry count, the CIFS format is ~25% larger.  An ACL
with a near-maximum count that passes the xattr-based size check in
cifs_set_acl() can overflow the heap allocation when written by
posix_acl_to_cifs().

Before converting, validate that the CIFS format size fits in the
remaining buffer space.  Replace the bogus hardcoded MaxDataCount
value (1000) with CIFSMaxBufSize.

Cc: stable@vger.kernel.org
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
---
 fs/smb/client/cifssmb.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index f5aad5f61dce..4ce22fe3cb2e 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	int rc = 0;
 	int bytes_returned = 0;
 	__u16 params, byte_count, data_count, param_offset, offset;
+	size_t cifs_acl_size, bytes_available;
 
 	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
 setAclRetry:
@@ -3575,7 +3576,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	params = 6 + name_len;
 	pSMB->MaxParameterCount = cpu_to_le16(2);
 	/* BB find max SMB size from sess */
-	pSMB->MaxDataCount = cpu_to_le16(1000);
+	pSMB->MaxDataCount = cpu_to_le16(CIFSMaxBufSize);
 	pSMB->MaxSetupCount = 0;
 	pSMB->Reserved = 0;
 	pSMB->Flags = 0;
@@ -3587,6 +3588,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	parm_data = ((char *)pSMB) + offset;
 	pSMB->ParameterOffset = cpu_to_le16(param_offset);
 
+	/* make sure we can fit the larger cifs_posix_aces in the buffer */
+	cifs_acl_size = sizeof(struct cifs_posix_acl) +
+		       (acl->a_count * sizeof(struct cifs_posix_ace));
+	bytes_available = (CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) - offset;
+	if (cifs_acl_size > bytes_available) {
+		rc = -E2BIG;
+		goto setACLerrorExit;
+	}
+
 	/* convert to on the wire format for POSIX ACL */
 	data_count = posix_acl_to_cifs(parm_data, acl, acl_type);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-25 18:32 [PATCH] smb: client: fix heap overflow in cifs_do_set_acl() from mismatched ACL format sizes Frank Sorenson
@ 2026-08-25 21:43 ` Frank Sorenson
  2026-08-26  0:03   ` Paulo Alcantara
  0 siblings, 1 reply; 7+ messages in thread
From: Frank Sorenson @ 2026-08-25 21:43 UTC (permalink / raw)
  To: linux-cifs; +Cc: pc, linkinjeon, stable

cifs_set_acl() validates ACL size using posix_acl_xattr_size():

        4 + (count * 8)  // 4-byte header + 8 bytes per ACE

cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
wire format into the same buffer:

        6 + (count * 10)  // 6-byte header + 10 bytes per ACE

An ACL that passes the xattr-based check in cifs_set_acl() can
overflow the heap when posix_acl_to_cifs() writes the larger CIFS
format.

Validate the CIFS format size against the remaining buffer space and
USHRT_MAX before converting--data_count is __u16, so sizes above
USHRT_MAX truncate the on-wire packet length, causing the server to
apply a partial ACL.  Replace MaxDataCount = 1000 with
min(CIFSMaxBufSize, USHRT_MAX).

Fixes: dc1af4c4b4721 ("cifs: implement set acl method")
Cc: stable@vger.kernel.org
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
---
v2 changes:
 - Add USHRT_MAX bound to prevent u16 truncation of data_count for ACLs
   with more than 6553 entries, which would cause a partial ACL to be
   silently applied on the server
 - limit MaxDataCount to min(CIFSMaxBufSize, USHRT_MAX)

 fs/smb/client/cifssmb.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index f5aad5f61dce..621aca5d3b75 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	int rc = 0;
 	int bytes_returned = 0;
 	__u16 params, byte_count, data_count, param_offset, offset;
+	size_t cifs_acl_size, bytes_available;
 
 	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
 setAclRetry:
@@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	}
 	params = 6 + name_len;
 	pSMB->MaxParameterCount = cpu_to_le16(2);
-	/* BB find max SMB size from sess */
-	pSMB->MaxDataCount = cpu_to_le16(1000);
+	pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
 	pSMB->MaxSetupCount = 0;
 	pSMB->Reserved = 0;
 	pSMB->Flags = 0;
@@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	parm_data = ((char *)pSMB) + offset;
 	pSMB->ParameterOffset = cpu_to_le16(param_offset);
 
+	/* make sure we can fit the larger cifs_posix_aces in the buffer */
+	cifs_acl_size = sizeof(struct cifs_posix_acl) +
+		       (acl->a_count * sizeof(struct cifs_posix_ace));
+	bytes_available = (CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) - offset;
+	if (cifs_acl_size > bytes_available || cifs_acl_size > USHRT_MAX) {
+		rc = -E2BIG;
+		goto setACLerrorExit;
+	}
+
 	/* convert to on the wire format for POSIX ACL */
 	data_count = posix_acl_to_cifs(parm_data, acl, acl_type);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-25 21:43 ` [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl() Frank Sorenson
@ 2026-08-26  0:03   ` Paulo Alcantara
  2026-08-26  1:47     ` Frank Sorenson
  0 siblings, 1 reply; 7+ messages in thread
From: Paulo Alcantara @ 2026-08-26  0:03 UTC (permalink / raw)
  To: Frank Sorenson, linux-cifs; +Cc: linkinjeon, stable

Frank Sorenson <sorenson@redhat.com> writes:

> cifs_set_acl() validates ACL size using posix_acl_xattr_size():
>
>         4 + (count * 8)  // 4-byte header + 8 bytes per ACE
>
> cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
> wire format into the same buffer:
>
>         6 + (count * 10)  // 6-byte header + 10 bytes per ACE
>
> An ACL that passes the xattr-based check in cifs_set_acl() can
> overflow the heap when posix_acl_to_cifs() writes the larger CIFS
> format.
>
> Validate the CIFS format size against the remaining buffer space and
> USHRT_MAX before converting--data_count is __u16, so sizes above
> USHRT_MAX truncate the on-wire packet length, causing the server to
> apply a partial ACL.  Replace MaxDataCount = 1000 with
> min(CIFSMaxBufSize, USHRT_MAX).
>
> Fixes: dc1af4c4b4721 ("cifs: implement set acl method")
> Cc: stable@vger.kernel.org
> Signed-off-by: Frank Sorenson <sorenson@redhat.com>
> ---
> v2 changes:
>  - Add USHRT_MAX bound to prevent u16 truncation of data_count for ACLs
>    with more than 6553 entries, which would cause a partial ACL to be
>    silently applied on the server
>  - limit MaxDataCount to min(CIFSMaxBufSize, USHRT_MAX)
>
>  fs/smb/client/cifssmb.c | 13 +++++++++++--
>  1 file changed, 11 insertions(+), 2 deletions(-)
>
> diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
> index f5aad5f61dce..621aca5d3b75 100644
> --- a/fs/smb/client/cifssmb.c
> +++ b/fs/smb/client/cifssmb.c
> @@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>  	int rc = 0;
>  	int bytes_returned = 0;
>  	__u16 params, byte_count, data_count, param_offset, offset;
> +	size_t cifs_acl_size, bytes_available;
>  
>  	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
>  setAclRetry:
> @@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>  	}
>  	params = 6 + name_len;
>  	pSMB->MaxParameterCount = cpu_to_le16(2);
> -	/* BB find max SMB size from sess */
> -	pSMB->MaxDataCount = cpu_to_le16(1000);
> +	pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
>  	pSMB->MaxSetupCount = 0;
>  	pSMB->Reserved = 0;
>  	pSMB->Flags = 0;
> @@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>  	parm_data = ((char *)pSMB) + offset;
>  	pSMB->ParameterOffset = cpu_to_le16(param_offset);
>  
> +	/* make sure we can fit the larger cifs_posix_aces in the buffer */
> +	cifs_acl_size = sizeof(struct cifs_posix_acl) +
> +		       (acl->a_count * sizeof(struct cifs_posix_ace));
> +	bytes_available = (CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) - offset;

Are you sure you want to use MAX_SMB2_HDR_SIZE?  This is SMB1 code, so I
would expect to see MAX_CIFS_HDR_SIZE.  Alternatively, use
MAX_HEADER_SIZE() helper.

Do you have any reproducer?

What server did you test these changes against?

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-26  0:03   ` Paulo Alcantara
@ 2026-08-26  1:47     ` Frank Sorenson
  2026-08-26 22:52       ` Paulo Alcantara
  0 siblings, 1 reply; 7+ messages in thread
From: Frank Sorenson @ 2026-08-26  1:47 UTC (permalink / raw)
  To: Paulo Alcantara, linux-cifs; +Cc: linkinjeon

[-- Attachment #1: Type: text/plain, Size: 6382 bytes --]


On 8/25/26 7:03 PM, Paulo Alcantara wrote:
> Frank Sorenson <sorenson@redhat.com> writes:
>
>> cifs_set_acl() validates ACL size using posix_acl_xattr_size():
>>
>>          4 + (count * 8)  // 4-byte header + 8 bytes per ACE
>>
>> cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
>> wire format into the same buffer:
>>
>>          6 + (count * 10)  // 6-byte header + 10 bytes per ACE
>>
>> An ACL that passes the xattr-based check in cifs_set_acl() can
>> overflow the heap when posix_acl_to_cifs() writes the larger CIFS
>> format.
>>
>> Validate the CIFS format size against the remaining buffer space and
>> USHRT_MAX before converting--data_count is __u16, so sizes above
>> USHRT_MAX truncate the on-wire packet length, causing the server to
>> apply a partial ACL.  Replace MaxDataCount = 1000 with
>> min(CIFSMaxBufSize, USHRT_MAX).
>>
>> Fixes: dc1af4c4b4721 ("cifs: implement set acl method")
>> Cc: stable@vger.kernel.org
>> Signed-off-by: Frank Sorenson <sorenson@redhat.com>
>> ---
>> v2 changes:
>>   - Add USHRT_MAX bound to prevent u16 truncation of data_count for ACLs
>>     with more than 6553 entries, which would cause a partial ACL to be
>>     silently applied on the server
>>   - limit MaxDataCount to min(CIFSMaxBufSize, USHRT_MAX)
>>
>>   fs/smb/client/cifssmb.c | 13 +++++++++++--
>>   1 file changed, 11 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
>> index f5aad5f61dce..621aca5d3b75 100644
>> --- a/fs/smb/client/cifssmb.c
>> +++ b/fs/smb/client/cifssmb.c
>> @@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>   	int rc = 0;
>>   	int bytes_returned = 0;
>>   	__u16 params, byte_count, data_count, param_offset, offset;
>> +	size_t cifs_acl_size, bytes_available;
>>   
>>   	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
>>   setAclRetry:
>> @@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>   	}
>>   	params = 6 + name_len;
>>   	pSMB->MaxParameterCount = cpu_to_le16(2);
>> -	/* BB find max SMB size from sess */
>> -	pSMB->MaxDataCount = cpu_to_le16(1000);
>> +	pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
>>   	pSMB->MaxSetupCount = 0;
>>   	pSMB->Reserved = 0;
>>   	pSMB->Flags = 0;
>> @@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>   	parm_data = ((char *)pSMB) + offset;
>>   	pSMB->ParameterOffset = cpu_to_le16(param_offset);
>>   
>> +	/* make sure we can fit the larger cifs_posix_aces in the buffer */
>> +	cifs_acl_size = sizeof(struct cifs_posix_acl) +
>> +		       (acl->a_count * sizeof(struct cifs_posix_ace));
>> +	bytes_available = (CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) - offset;
> Are you sure you want to use MAX_SMB2_HDR_SIZE?  This is SMB1 code, so I
> would expect to see MAX_CIFS_HDR_SIZE.  Alternatively, use
> MAX_HEADER_SIZE() helper.

The only reason I used MAX_SMB2_HDR_SIZE is that it's the actual size 
allocated in cifs_buf_get() from cifs_req_cachep...  but now that I 
think about it, we don't actually want to use the extra 
(MAX_SMB2_HDR_SIZE - MAX_CIFS_HDR_SIZE) bytes, even though we have it 
allocated.  So as you say, MAX_CIFS_HDR_SIZE or 
MAX_HEADER_SIZE(tcon->ses->server) would be better.

I can respin.

> Do you have any reproducer?

Yes, I'll attach the script.  It sets 1800 named user ACEs, which is 
14436 xattr bytes, ~18046 CIFS bytes

7.2-ish unpatched:

# mount //vm25/user1 /mnt/vm25 -overs=1.0,username=user1,password=pass,unix
# python3 reproduce_acl_overflow.py /mnt/vm25/testfile
Setting ACL with 1800 named user ACEs (14436 xattr bytes, ~18046 CIFS bytes)
Unpatched kernel: expect KASAN report or silent heap corruption
Patched kernel:   expect -E2BIG from setxattr
setxattr failed: [Errno 5] Input/output error: '/mnt/vm25/testfile'

[ 1068.599600] BUG: KASAN: slab-out-of-bounds in 
posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.599952] Write of size 8 at addr ffff88810625c0ca by task 
python3/40835

[ 1068.600017] CPU: 2 UID: 0 PID: 40835 Comm: python3 Not tainted 
7.2.0-rc7+ #6 PREEMPT(full)
[ 1068.600029] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 
1.17.0-10.fc44 06/10/2025
[ 1068.600041] Call Trace:
[ 1068.600047]  <TASK>
[ 1068.600050]  dump_stack_lvl+0x4e/0x70
[ 1068.600066]  ? posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.600312]  print_address_description.constprop.0+0x70/0x300
[ 1068.600324]  ? posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.600570]  print_report+0x108/0x209
[ 1068.600580]  ? posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.600818]  kasan_report+0xf0/0x120
[ 1068.600827]  ? posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.601059]  posix_acl_to_cifs+0x60a/0x6b0 [cifs]
[ 1068.601292]  cifs_do_set_acl+0x446/0xb90 [cifs]
...

[ 1068.616380] Allocated by task 40835:
[ 1068.617021]  kasan_save_stack+0x30/0x50
[ 1068.617033]  kasan_save_track+0x14/0x30
[ 1068.617039]  __kasan_slab_alloc+0x89/0x90
[ 1068.617045]  kmem_cache_alloc_noprof+0x155/0x430
[ 1068.617053]  mempool_alloc_noprof+0x11b/0x1e0
[ 1068.617062]  cifs_buf_get+0x36/0x90 [cifs]
[ 1068.617344]  smb_init+0x43/0x100 [cifs]
[ 1068.617618]  cifs_do_set_acl+0x147/0xb90 [cifs]
[ 1068.617860]  cifs_set_acl+0x717/0x920 [cifs]
[ 1068.618096]  vfs_set_acl+0x35a/0x880
[ 1068.618104]  do_set_acl+0xa6/0x160
[ 1068.618109]  filename_setxattr+0x129/0x170
[ 1068.618115]  path_setxattrat+0x156/0x290
[ 1068.618120]  __x64_sys_setxattr+0xc6/0x140
[ 1068.618125]  do_syscall_64+0xe3/0x540
[ 1068.618133]  entry_SYSCALL_64_after_hwframe+0x76/0x7e

[ 1068.618793] The buggy address belongs to the object at ffff888106258000
                 which belongs to the cache cifs_request of size 16588
[ 1068.619990] The buggy address is located 16586 bytes inside of
                 allocated 16588-byte region [ffff888106258000, 
ffff88810625c0cc)

7.2 patched

# python3 reproduce_acl_overflow.py /mnt/vm25/testfile Setting ACL with 
1800 named user ACEs (14436 xattr bytes, ~18046 CIFS bytes) Unpatched 
kernel: expect KASAN report or silent heap corruption Patched kernel: 
expect -E2BIG from setxattr Got -E2BIG -- fix is active

> What server did you test these changes against?

samba


-- 
Frank Sorenson
sorenson@redhat.com
Principal Software Maintenance Engineer, filesystems
Red Hat

[-- Attachment #2: reproduce_acl_overflow.py --]
[-- Type: text/x-python, Size: 2396 bytes --]

#!/usr/bin/env python3
"""
Reproducer for cifs_do_set_acl() heap overflow.

cifs_set_acl() validates ACL size using Linux xattr format (8 bytes/ACE),
but posix_acl_to_cifs() writes CIFS wire format (10 bytes/ACE) into the
same buffer.  With CIFSMaxBufSize=16384 (default), overflow zone is
~1650-2047 named user ACEs.

Requires Samba share mounted with Unix extensions:
  mount -t cifs //server/share /mnt -o unix,username=user,password=pass

Usage: python3 reproduce_acl_overflow.py <path_on_cifs_mount>
"""
import os, sys, struct

POSIX_ACL_XATTR_VERSION = 2
ACL_USER_OBJ  = 0x0001
ACL_USER      = 0x0002
ACL_GROUP_OBJ = 0x0004
ACL_MASK      = 0x0010
ACL_OTHER     = 0x0020
ACL_READ      = 0x04

def build_acl(n_user_aces):
    """Build a valid POSIX ACL xattr blob with n_user_aces named user entries."""
    entries = [(ACL_USER_OBJ, ACL_READ, 0xffffffff)]
    for uid in range(1, n_user_aces + 1):
        entries.append((ACL_USER, ACL_READ, uid))
    entries += [
        (ACL_GROUP_OBJ, ACL_READ, 0xffffffff),
        (ACL_MASK,      ACL_READ, 0xffffffff),
        (ACL_OTHER,     0,        0xffffffff),
    ]
    blob = struct.pack("<I", POSIX_ACL_XATTR_VERSION)
    for tag, perm, uid in entries:
        blob += struct.pack("<HHI", tag, perm, uid)
    return blob

def main():
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <path_on_cifs_mount>")
        sys.exit(1)

    path = sys.argv[1]
    if not os.path.exists(path):
        open(path, 'w').close()

    # 1800 ACEs: comfortably in the overflow zone for default CIFSMaxBufSize=16384
    # xattr size: 4 + 1804*8 = 14436 bytes  (passes cifs_set_acl check)
    # cifs  size: 6 + 1804*10 = 18046 bytes (overflows ~16588-byte buffer)
    n_aces = 1800
    blob = build_acl(n_aces)
    print(f"Setting ACL with {n_aces} named user ACEs "
          f"({len(blob)} xattr bytes, ~{6 + (n_aces + 4) * 10} CIFS bytes)")
    print("Unpatched kernel: expect KASAN report or silent heap corruption")
    print("Patched kernel:   expect -E2BIG from setxattr")

    try:
        os.setxattr(path, "system.posix_acl_access", blob)
        print("setxattr succeeded (unexpected -- server accepted it)")
    except OSError as e:
        import errno
        if e.errno == errno.E2BIG:
            print("Got -E2BIG -- fix is active")
        else:
            print(f"setxattr failed: {e}")

if __name__ == "__main__":
    main()

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-26  1:47     ` Frank Sorenson
@ 2026-08-26 22:52       ` Paulo Alcantara
  2026-08-27  2:13         ` [PATCH v4] " Frank Sorenson
  0 siblings, 1 reply; 7+ messages in thread
From: Paulo Alcantara @ 2026-08-26 22:52 UTC (permalink / raw)
  To: sorenson, linux-cifs; +Cc: linkinjeon

Frank Sorenson <sorenson@redhat.com> writes:

> On 8/25/26 7:03 PM, Paulo Alcantara wrote:
>> Frank Sorenson <sorenson@redhat.com> writes:
>>
>>> cifs_set_acl() validates ACL size using posix_acl_xattr_size():
>>>
>>>          4 + (count * 8)  // 4-byte header + 8 bytes per ACE
>>>
>>> cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
>>> wire format into the same buffer:
>>>
>>>          6 + (count * 10)  // 6-byte header + 10 bytes per ACE
>>>
>>> An ACL that passes the xattr-based check in cifs_set_acl() can
>>> overflow the heap when posix_acl_to_cifs() writes the larger CIFS
>>> format.
>>>
>>> Validate the CIFS format size against the remaining buffer space and
>>> USHRT_MAX before converting--data_count is __u16, so sizes above
>>> USHRT_MAX truncate the on-wire packet length, causing the server to
>>> apply a partial ACL.  Replace MaxDataCount = 1000 with
>>> min(CIFSMaxBufSize, USHRT_MAX).
>>>
>>> Fixes: dc1af4c4b4721 ("cifs: implement set acl method")
>>> Cc: stable@vger.kernel.org
>>> Signed-off-by: Frank Sorenson <sorenson@redhat.com>
>>> ---
>>> v2 changes:
>>>   - Add USHRT_MAX bound to prevent u16 truncation of data_count for ACLs
>>>     with more than 6553 entries, which would cause a partial ACL to be
>>>     silently applied on the server
>>>   - limit MaxDataCount to min(CIFSMaxBufSize, USHRT_MAX)
>>>
>>>   fs/smb/client/cifssmb.c | 13 +++++++++++--
>>>   1 file changed, 11 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
>>> index f5aad5f61dce..621aca5d3b75 100644
>>> --- a/fs/smb/client/cifssmb.c
>>> +++ b/fs/smb/client/cifssmb.c
>>> @@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>>   	int rc = 0;
>>>   	int bytes_returned = 0;
>>>   	__u16 params, byte_count, data_count, param_offset, offset;
>>> +	size_t cifs_acl_size, bytes_available;
>>>   
>>>   	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
>>>   setAclRetry:
>>> @@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>>   	}
>>>   	params = 6 + name_len;
>>>   	pSMB->MaxParameterCount = cpu_to_le16(2);
>>> -	/* BB find max SMB size from sess */
>>> -	pSMB->MaxDataCount = cpu_to_le16(1000);
>>> +	pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
>>>   	pSMB->MaxSetupCount = 0;
>>>   	pSMB->Reserved = 0;
>>>   	pSMB->Flags = 0;
>>> @@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
>>>   	parm_data = ((char *)pSMB) + offset;
>>>   	pSMB->ParameterOffset = cpu_to_le16(param_offset);
>>>   
>>> +	/* make sure we can fit the larger cifs_posix_aces in the buffer */
>>> +	cifs_acl_size = sizeof(struct cifs_posix_acl) +
>>> +		       (acl->a_count * sizeof(struct cifs_posix_ace));
>>> +	bytes_available = (CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) - offset;
>> Are you sure you want to use MAX_SMB2_HDR_SIZE?  This is SMB1 code, so I
>> would expect to see MAX_CIFS_HDR_SIZE.  Alternatively, use
>> MAX_HEADER_SIZE() helper.
>
> The only reason I used MAX_SMB2_HDR_SIZE is that it's the actual size 
> allocated in cifs_buf_get() from cifs_req_cachep...  but now that I 
> think about it, we don't actually want to use the extra 
> (MAX_SMB2_HDR_SIZE - MAX_CIFS_HDR_SIZE) bytes, even though we have it 
> allocated.  So as you say, MAX_CIFS_HDR_SIZE or 
> MAX_HEADER_SIZE(tcon->ses->server) would be better.

Thanks for checking.

> I can respin.

Please.

>> Do you have any reproducer?
>
> Yes, I'll attach the script.  It sets 1800 named user ACEs, which is 
> 14436 xattr bytes, ~18046 CIFS bytes

Thanks.  That's really useful.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v4] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-26 22:52       ` Paulo Alcantara
@ 2026-08-27  2:13         ` Frank Sorenson
  2026-08-28  1:18           ` Paulo Alcantara
  0 siblings, 1 reply; 7+ messages in thread
From: Frank Sorenson @ 2026-08-27  2:13 UTC (permalink / raw)
  To: linux-cifs; +Cc: pc, linkinjeon, stable

cifs_set_acl() validates ACL size using posix_acl_xattr_size():

        4 + (count * 8)  // 4-byte header + 8 bytes per ACE

cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
wire format into the same buffer:

        6 + (count * 10)  // 6-byte header + 10 bytes per ACE

An ACL that passes the xattr-based check in cifs_set_acl() can
overflow the heap when posix_acl_to_cifs() writes the larger CIFS
format.

Validate the CIFS format size against the remaining buffer space and
USHRT_MAX before converting--data_count is __u16, so sizes above
USHRT_MAX truncate the on-wire packet length, causing the server to
apply a partial ACL.  Replace MaxDataCount = 1000 with
min(CIFSMaxBufSize, USHRT_MAX).

Fixes: dc1af4c4b4721 ("cifs: implement set acl method")
Cc: stable@vger.kernel.org
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
---
reproducer script which sets 1800 named user ACEs will overflow,
throwing KASAN error or silently corrupting heap

v3/4 changes:
 - change header size to use MAX_HEADER_SIZE helper

v2 changes:
 - Add USHRT_MAX bound to prevent u16 truncation of data_count for ACLs
   with more than 6553 entries, which would cause a partial ACL to be
   silently applied on the server
 - limit MaxDataCount to min(CIFSMaxBufSize, USHRT_MAX)

 fs/smb/client/cifssmb.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index f5aad5f61dce..230af243247c 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	int rc = 0;
 	int bytes_returned = 0;
 	__u16 params, byte_count, data_count, param_offset, offset;
+	size_t cifs_acl_size, bytes_available;
 
 	cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName);
 setAclRetry:
@@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	}
 	params = 6 + name_len;
 	pSMB->MaxParameterCount = cpu_to_le16(2);
-	/* BB find max SMB size from sess */
-	pSMB->MaxDataCount = cpu_to_le16(1000);
+	pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX));
 	pSMB->MaxSetupCount = 0;
 	pSMB->Reserved = 0;
 	pSMB->Flags = 0;
@@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
 	parm_data = ((char *)pSMB) + offset;
 	pSMB->ParameterOffset = cpu_to_le16(param_offset);
 
+	/* make sure we can fit the larger cifs_posix_aces in the buffer */
+	cifs_acl_size = sizeof(struct cifs_posix_acl) +
+		       (acl->a_count * sizeof(struct cifs_posix_ace));
+	bytes_available = (CIFSMaxBufSize + MAX_HEADER_SIZE(tcon->ses->server)) - offset;
+	if (cifs_acl_size > bytes_available || cifs_acl_size > USHRT_MAX) {
+		rc = -E2BIG;
+		goto setACLerrorExit;
+	}
+
 	/* convert to on the wire format for POSIX ACL */
 	data_count = posix_acl_to_cifs(parm_data, acl, acl_type);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v4] smb: client: fix heap overflow in cifs_do_set_acl()
  2026-08-27  2:13         ` [PATCH v4] " Frank Sorenson
@ 2026-08-28  1:18           ` Paulo Alcantara
  0 siblings, 0 replies; 7+ messages in thread
From: Paulo Alcantara @ 2026-08-28  1:18 UTC (permalink / raw)
  To: Frank Sorenson, linux-cifs; +Cc: linkinjeon, stable

Frank Sorenson <sorenson@redhat.com> writes:

> cifs_set_acl() validates ACL size using posix_acl_xattr_size():
>
>         4 + (count * 8)  // 4-byte header + 8 bytes per ACE
>
> cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS
> wire format into the same buffer:
>
>         6 + (count * 10)  // 6-byte header + 10 bytes per ACE
>
> An ACL that passes the xattr-based check in cifs_set_acl() can
> overflow the heap when posix_acl_to_cifs() writes the larger CIFS
> format.
>
> Validate the CIFS format size against the remaining buffer space and
> USHRT_MAX before converting--data_count is __u16, so sizes above
> USHRT_MAX truncate the on-wire packet length, causing the server to
> apply a partial ACL.  Replace MaxDataCount = 1000 with
> min(CIFSMaxBufSize, USHRT_MAX).
> ...

Applied.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-08-28  1:18 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 18:32 [PATCH] smb: client: fix heap overflow in cifs_do_set_acl() from mismatched ACL format sizes Frank Sorenson
2026-08-25 21:43 ` [PATCH v2] smb: client: fix heap overflow in cifs_do_set_acl() Frank Sorenson
2026-08-26  0:03   ` Paulo Alcantara
2026-08-26  1:47     ` Frank Sorenson
2026-08-26 22:52       ` Paulo Alcantara
2026-08-27  2:13         ` [PATCH v4] " Frank Sorenson
2026-08-28  1:18           ` Paulo Alcantara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox