CVE-2026-46159
Description
In the Linux kernel, the following vulnerability has been resolved:
btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
btrfs_ioctl_space_info() has a TOCTOU race between two passes over the block group RAID type lists. The first pass counts entries to determine the allocation size, then the second pass fills the buffer. The groups_sem rwlock is released between passes, allowing concurrent block group removal to reduce the entry count.
When the second pass fills fewer entries than the first pass counted, copy_to_user() copies the full alloc_size bytes including trailing uninitialized kmalloc bytes to userspace.
Fix by copying only total_spaces entries (the actually-filled count from the second pass) instead of alloc_size bytes, and switch to kzalloc so any future copy size mismatch cannot leak heap data.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A TOCTOU race in btrfs_ioctl_space_info() allows uninitialized heap data to be leaked to userspace.
Vulnerability
A time-of-check-time-of-use (TOCTOU) race condition exists in the btrfs_ioctl_space_info() function in the Linux kernel's btrfs filesystem. The function performs two passes over the block group RAID type lists: the first pass counts entries to allocate a buffer, and the second pass fills the buffer. The groups_sem rwlock is released between passes, allowing a concurrent block group removal to reduce the entry count. When the second pass fills fewer entries than counted, copy_to_user() copies the full allocated size, including trailing uninitialized heap bytes, to userspace. This affects Linux kernel versions prior to the commit that fixes the issue.
Exploitation
An attacker must have the ability to call the BTRFS_IOC_SPACE_INFO ioctl on a btrfs filesystem (typically available to unprivileged users) and concurrently trigger block group removal (e.g., via btrfs device remove or balance operations). The race window is between the two passes of the ioctl handler. By repeatedly invoking the ioctl while simultaneously removing block groups, the attacker can cause the second pass to fill fewer entries than expected, resulting in the leak of uninitialized kernel heap memory.
Impact
Successful exploitation leads to an information leak of uninitialized kernel heap data to userspace. The leaked memory may contain sensitive information such as pointers, cryptographic keys, or other kernel objects. The attacker gains this information from an unprivileged position, potentially aiding further exploitation of the system.
Mitigation
The fix is included in Linux kernel commit d09d67d5de577cedae3de9497dff217e0ac8b641, which switches to kzalloc and copies only the actual number of filled entries. Users should update to a kernel version containing this commit. No workaround is available other than restricting access to the BTRFS_IOC_SPACE_INFO ioctl via security modules or system call filtering.
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
1Patches
10f5ee467b5676btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 4723013995f5bb..d17d1eff8eff4e 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3087,7 +3087,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3143,7 +3143,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
4fdc6ee08021btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 45852dbf9dfbc2..a61022182f45d3 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3113,7 +3113,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3169,7 +3169,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
5d12e0ab009abtrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index bfe253c2849a54..c0691e93e0a58a 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3025,7 +3025,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3081,7 +3081,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
d09d67d5de57btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index d75d31b606e499..4a1d27e4884df0 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2897,7 +2897,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -2953,7 +2953,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) return -EFAULT; out: -- cgit 1.3-korg
973e57c726c1btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index a4d715bbed57ba..b2e447f5005c16 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2897,7 +2897,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -2953,7 +2953,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) return -EFAULT; out: -- cgit 1.3-korg
f5ee467b5676btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 4723013995f5bb..d17d1eff8eff4e 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3087,7 +3087,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3143,7 +3143,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
4fdc6ee08021btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 45852dbf9dfbc2..a61022182f45d3 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3113,7 +3113,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3169,7 +3169,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
5d12e0ab009abtrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index bfe253c2849a54..c0691e93e0a58a 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3025,7 +3025,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -3081,7 +3081,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) ret = -EFAULT; kfree(dest_orig); -- cgit 1.3-korg
d09d67d5de57btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index d75d31b606e499..4a1d27e4884df0 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2897,7 +2897,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -2953,7 +2953,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) return -EFAULT; out: -- cgit 1.3-korg
973e57c726c1btrfs: fix btrfs_ioctl_space_info() slot_count TOCTOU which can lead to info-leak
1 file changed · +3 −3
fs/btrfs/ioctl.c+3 −3 modifieddiff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index a4d715bbed57ba..b2e447f5005c16 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2897,7 +2897,7 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, return -ENOMEM; space_args.total_spaces = 0; - dest = kmalloc(alloc_size, GFP_KERNEL); + dest = kzalloc(alloc_size, GFP_KERNEL); if (!dest) return -ENOMEM; dest_orig = dest; @@ -2953,7 +2953,8 @@ static long btrfs_ioctl_space_info(struct btrfs_fs_info *fs_info, user_dest = (struct btrfs_ioctl_space_info __user *) (arg + sizeof(struct btrfs_ioctl_space_args)); - if (copy_to_user(user_dest, dest_orig, alloc_size)) + if (copy_to_user(user_dest, dest_orig, + space_args.total_spaces * sizeof(*dest_orig))) return -EFAULT; out: -- cgit 1.3-korg
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
5- git.kernel.org/stable/c/4fdc6ee0802121d9cd96b8d085e589f51e5a4ec3nvd
- git.kernel.org/stable/c/5d12e0ab009ade48c1bff9324fd9bea2c773d088nvd
- git.kernel.org/stable/c/973e57c726c1f8e77259d1c8e519519f1e9aea77nvd
- git.kernel.org/stable/c/d09d67d5de577cedae3de9497dff217e0ac8b641nvd
- git.kernel.org/stable/c/f5ee467b56764964027c361641f64953fc0f8f9anvd
News mentions
0No linked articles in our index yet.