Skip to content

Commit 1c0ae3d

Browse files
Ibrahim Hashimovsmfrench
authored andcommitted
ksmbd: fix integer overflow in set_file_allocation_info()
set_file_allocation_info() converts the client-supplied FILE_ALLOCATION_INFORMATION::AllocationSize into a 512-byte block count with: alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9; AllocationSize is a fully client-controlled __le64 field; the only validation performed by the caller (smb2_set_info_file(), case FILE_ALLOCATION_INFORMATION) is that the fixed buffer is at least sizeof(struct smb2_file_alloc_info) == 8 bytes. The value itself is never range-checked before this arithmetic. When AllocationSize is close to U64_MAX (e.g. 0xffffffffffffffff), "AllocationSize + 511" wraps around mod 2^64 to a small number (0xffffffffffffffff + 511 = 510), so alloc_blks becomes 0. Since any existing regular file has stat.blocks > 0, the function then takes the "shrink" branch and calls: ksmbd_vfs_truncate(work, fp, alloc_blks * 512); /* == 0 */ silently truncating the file to size 0, even though the client asked to grow the allocation to (what looks like) the maximum possible size. The trailing "if (size < alloc_blks * 512) i_size_write(inode, size);" restore is guarded by a comparison that is never true once alloc_blks == 0, so the truncation is not undone. This lets an authenticated SMB client that already holds an open handle with FILE_WRITE_DATA on a file silently truncate that same file to size 0 via a single crafted SET_INFO(FILE_ALLOCATION_INFORMATION) request advertising a near-U64_MAX AllocationSize, even though the request asks to grow the file's allocation rather than shrink it. This is a functional/data-loss bug, not a privilege-boundary violation: the same client could already truncate the file via FILE_END_OF_FILE_INFORMATION or a plain write. Fix it by validating AllocationSize against MAX_LFS_FILESIZE, the same upper bound the VFS itself uses to reject unrepresentable file sizes, before doing the "+511" rounding, and rejecting oversized values with -EINVAL. Bounding AllocationSize to MAX_LFS_FILESIZE - 511 guarantees the "+511" addition cannot wrap, and that the subsequent "alloc_blks * 512" values passed to vfs_fallocate() and ksmbd_vfs_truncate() stay within a representable loff_t as well. No legitimate SMB client asks for an allocation size anywhere near 2^64 bytes, so this only rejects a value that was previously silently misinterpreted as zero. Runtime-verified on a v6.19 KASAN test stand: sending SET_INFO (FILE_ALLOCATION_INFORMATION) with AllocationSize = 0xffffffffffffffff against ksmbd now returns -EINVAL and leaves the target file's size unchanged, where the unpatched kernel truncated it from 4096 to 0 bytes. Fixes: e2f3448 ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov <security@auditcode.ai> Assisted-by: AuditCode-AI:2026.07 Acked-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
1 parent a13c140 commit 1c0ae3d

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

fs/smb/server/smb2pdu.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6781,6 +6781,7 @@ static int set_file_allocation_info(struct ksmbd_work *work,
67816781
*/
67826782

67836783
loff_t alloc_blks;
6784+
u64 alloc_size;
67846785
struct inode *inode;
67856786
struct kstat stat;
67866787
int rc;
@@ -6796,7 +6797,19 @@ static int set_file_allocation_info(struct ksmbd_work *work,
67966797
if (rc)
67976798
return rc;
67986799

6799-
alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9;
6800+
/*
6801+
* AllocationSize is fully client-controlled (the caller only
6802+
* validates the fixed 8-byte buffer length). Reject values that
6803+
* would overflow the "round up to 512-byte blocks" conversion
6804+
* below instead of silently wrapping it to a tiny block count,
6805+
* which would truncate the file to a size the client never
6806+
* asked for.
6807+
*/
6808+
alloc_size = le64_to_cpu(file_alloc_info->AllocationSize);
6809+
if (alloc_size > MAX_LFS_FILESIZE - 511)
6810+
return -EINVAL;
6811+
6812+
alloc_blks = (alloc_size + 511) >> 9;
68006813
inode = file_inode(fp->filp);
68016814

68026815
if (alloc_blks > stat.blocks) {

0 commit comments

Comments
 (0)