CVE-2026-46155
Description
In the Linux kernel, the following vulnerability has been resolved:
smb/client: fix out-of-bounds read in smb2_compound_op()
If a server sends a truncated response but a large OutputBufferLength, and terminates the EA list early, check_wsl_eas() returns success without validating that the entire OutputBufferLength fits within iov_len.
Then smb2_compound_op() does: memcpy(idata->wsl.eas, data[0], size[0]);
Where size[0] is OutputBufferLength. If iov_len is smaller than size[0], memcpy can read beyond the end of the rsp_iov allocation and leak adjacent kernel heap memory.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Out-of-bounds read in Linux kernel's SMB2 compound operation due to insufficient validation of OutputBufferLength against iov_len, allowing heap memory leak.
Vulnerability
In the Linux kernel's SMB client, the function smb2_compound_op() contains an out-of-bounds read vulnerability. When a server sends a truncated response with a large OutputBufferLength and terminates the EA list early, check_wsl_eas() returns success without validating that the entire OutputBufferLength fits within iov_len. Subsequently, smb2_compound_op() executes memcpy(idata->wsl.eas, data[0], size[0]) where size[0] is OutputBufferLength. If iov_len is smaller than size[0], the memcpy reads beyond the end of the rsp_iov allocation. This affects Linux kernel versions prior to the fix commit [1].
Exploitation
An attacker must be able to send a crafted SMB2 response to a client, either by controlling a malicious SMB server or by performing a man-in-the-middle attack on the SMB connection. The client must use the SMB2 protocol and trigger the compound operation path. The attacker sends a truncated response with a large OutputBufferLength and an early-terminated EA list, causing check_wsl_eas() to return success without proper bounds checking. The subsequent memcpy then reads beyond the allocated buffer.
Impact
Successful exploitation results in an out-of-bounds read from kernel heap memory, potentially leaking adjacent sensitive data. This constitutes an information disclosure vulnerability. The attacker does not gain code execution directly, but leaked memory contents could be used to bypass security mechanisms or aid further exploitation.
Mitigation
The fix is provided in Linux kernel commit 9b3af35645ff9cd334edc130249f9a2fb2bea25f [1]. Users should update to a kernel version that includes this commit. No workaround is documented. The vulnerability is not listed on the CISA Known Exploited Vulnerabilities (KEV) catalog as of publication.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
10dffb44b2e06asmb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index aae486771ccb1b..540f4ba81b6ce4 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
9b3af35645ffsmb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 97e015b512602a..4f08c808e4a3e3 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
512d33bc8ea4smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 17052b988951f3..e13bc8a97c4497 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
8d09328dfda0smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 286912616c7339..6c9c229b91f654 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -111,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -120,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
a16f70a71be4smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index fe1c9d7765806d..3b09cf8ab0f276 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -111,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -120,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
8d09328dfda0smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 286912616c7339..6c9c229b91f654 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -111,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -120,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
512d33bc8ea4smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 17052b988951f3..e13bc8a97c4497 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
9b3af35645ffsmb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 97e015b512602a..4f08c808e4a3e3 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
dffb44b2e06asmb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index aae486771ccb1b..540f4ba81b6ce4 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -108,7 +108,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +117,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
a16f70a71be4smb/client: fix out-of-bounds read in smb2_compound_op()
1 file changed · +8 −5
fs/smb/client/smb2inode.c+8 −5 modifieddiff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index fe1c9d7765806d..3b09cf8ab0f276 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -111,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -120,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea->ea_data + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing bounds check in check_wsl_eas() allows server-supplied OutputBufferLength to exceed the actual iovec buffer size, leading to an out-of-bounds read in smb2_compound_op()."
Attack vector
An attacker controlling a malicious SMB server sends a truncated SMB2 response with a large `OutputBufferLength` field and an EA list that terminates early. The function `check_wsl_eas()` validates EA entries only against the iovec boundary (`iov_len`) but returns success without checking whether the claimed `OutputBufferLength` exceeds that boundary. The caller `smb2_compound_op()` then performs `memcpy(idata->wsl.eas, data[0], size[0])` where `size[0]` is the attacker-controlled `OutputBufferLength`, causing an out-of-bounds read beyond the `rsp_iov` allocation that can leak adjacent kernel heap memory [patch_id=2898225].
Affected code
The vulnerable function is `check_wsl_eas()` in `fs/smb/client/smb2inode.c`. The function validates EA entries against `end` (computed as `rsp_iov->iov_base + rsp_iov->iov_len`) but does not verify that the server-supplied `OutputBufferLength` (`outlen`) fits within the actual iovec length before the caller `smb2_compound_op()` uses `size[0]` (which is `OutputBufferLength`) in a `memcpy()` call [patch_id=2898225].
What the fix does
The patch replaces the single `end` pointer (which pointed to the end of the iovec buffer) with two separate pointers: `ea_end` (computed as `ea + outlen`) and `iov_end` (computed as `rsp_iov->iov_base + rsp_iov->iov_len`). A new early check `if (ea_end > iov_end) return -EINVAL` ensures that the server-claimed `OutputBufferLength` does not extend beyond the actual received data. All subsequent bounds checks in the EA parsing loop are changed from comparing against `end` to comparing against `ea_end`, so the validation is correctly scoped to the server-claimed output length rather than the raw iovec length [patch_id=2898225].
Preconditions
- networkThe client must mount a share from a malicious SMB server that can craft a truncated SMB2 response with a large OutputBufferLength
- inputThe client must issue a SMB2_OP_QUERY_WSL_EA compound operation (triggered by WSL extended attribute queries)
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/512d33bc8ea4ea5c19728ee118715f4b1f4d1926nvd
- git.kernel.org/stable/c/8d09328dfda089675e4c049f3f256064a1d1996bnvd
- git.kernel.org/stable/c/9b3af35645ff9cd334edc130249f9a2fb2bea25fnvd
- git.kernel.org/stable/c/a16f70a71be4b5a4eccf39a9bf09b47285f4cb7cnvd
- git.kernel.org/stable/c/dffb44b2e06a2908e249f0f93156fc987eee1d1cnvd
News mentions
0No linked articles in our index yet.