CVE-2026-46107
Description
In the Linux kernel, the following vulnerability has been resolved:
dm-thin: fix metadata refcount underflow
There's a bug in dm-thin in the function rebalance_children. If the internal btree node has one entry, the code tries to copy all btree entries from the node's child to the node itself and then decrement the child's reference count.
If the child node is shared (it has reference count > 1), we won't free it, so there would be two pointers to each of the grandchildren nodes. But the reference counts of the grandchildren is not increased, thus the reference count doesn't match the number of pointers that point to the grandchildren. This results in "device mapper: space map common: unable to decrement block" errors.
Fix this bug by incrementing reference counts on the grandchildren if the btree node is shared.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A refcount underflow in dm-thin's rebalance_children function can cause metadata corruption in the Linux kernel.
Vulnerability
In the Linux kernel's device-mapper thin provisioning (dm-thin) subsystem, the function rebalance_children contains a reference-counting bug. When an internal btree node has exactly one entry, the code copies all btree entries from the node's child to the node itself and then decrements the child's reference count. If the child node is shared (reference count > 1), the grandchildren nodes are not freed, but their reference counts are not incremented to account for the new pointers. This mismatch leads to a refcount underflow, resulting in "device mapper: space map common: unable to decrement block" errors. The vulnerability affects Linux kernel versions prior to the fix commit [1].
Exploitation
An attacker would need the ability to create and manipulate dm-thin volumes, which typically requires local access and sufficient privileges (e.g., root or CAP_SYS_ADMIN). By crafting specific metadata patterns that cause a btree node to have a single entry and a shared child, the attacker can trigger the refcount underflow. No user interaction beyond normal administrative operations on thin provisioning devices is required.
Impact
Successful exploitation leads to metadata corruption within the dm-thin space map. The immediate symptom is the inability to decrement blocks, which can cause further metadata inconsistencies, potential data loss, and denial of service. The integrity and availability of the thin provisioning storage are compromised.
Mitigation
The vulnerability is fixed in Linux kernel commit 85311a585a26640760cd0f3349ab9f2905691044 [1]. Users should update their kernel to a version containing this fix. No workaround is documented; the only mitigation is to apply the patch.
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
1085311a585a26dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
5ec0debbcfd4dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
09a65adc7d8bdm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
323d252a4a37dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
12161e03d33adm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
09a65adc7d8bdm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
323d252a4a37dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
5ec0debbcfd4dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
85311a585a26dm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
12161e03d33adm-thin: fix metadata refcount underflow
1 file changed · +8 −1
drivers/md/persistent-data/dm-btree-remove.c+8 −1 modifieddiff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index 942cd47eb52dac..aeec5b9a1dd5c5 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -490,12 +490,20 @@ static int rebalance_children(struct shadow_spine *s, if (le32_to_cpu(n->header.nr_entries) == 1) { struct dm_block *child; + int is_shared; dm_block_t b = value64(n, 0); + r = dm_tm_block_is_shared(info->tm, b, &is_shared); + if (r) + return r; + r = dm_tm_read_lock(info->tm, b, &btree_node_validator, &child); if (r) return r; + if (is_shared) + inc_children(info->tm, dm_block_data(child), vt); + memcpy(n, dm_block_data(child), dm_bm_block_size(dm_tm_get_bm(info->tm))); -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing reference count increment on grandchildren when a shared child node's entries are copied into the parent during btree rebalancing."
Attack vector
An attacker with the ability to trigger btree rebalancing operations on a dm-thin metadata device can exploit this refcount underflow. When `rebalance_children` encounters an internal btree node with one entry whose child is shared (reference count > 1), the code copies the child's contents into the parent without incrementing the reference counts of the grandchildren. This creates two pointers to each grandchild while the reference count remains unchanged, leading to a mismatch that eventually causes "device mapper: space map common: unable to decrement block" errors and potential metadata corruption.
Affected code
The bug resides in the function `rebalance_children` in `drivers/md/persistent-data/dm-btree-remove.c` [patch_id=2898665]. When the internal btree node has exactly one entry, the code copies all btree entries from the child node into the parent node via `memcpy` and then decrements the child's reference count.
What the fix does
The patch adds a call to `dm_tm_block_is_shared` before reading the child block to determine whether the child node is shared [patch_id=2898665]. If the child is shared (`is_shared` is true), the patch calls `inc_children` on the child's data to increment the reference counts of all grandchildren before the `memcpy` copies the child's contents into the parent. This ensures that the grandchild reference counts correctly reflect the number of pointers pointing to them, preventing the refcount underflow that caused the "unable to decrement block" errors.
Preconditions
- authThe attacker must be able to trigger btree rebalancing operations on a dm-thin metadata device, which requires local access to create and manipulate thin-provisioned volumes.
- inputThe internal btree node must have exactly one entry, and its child node must be shared (reference count > 1).
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/09a65adc7d8bbfce06392cb6d375468e2728ead5nvd
- git.kernel.org/stable/c/12161e03d33afce781f68fa11cc6060538862fadnvd
- git.kernel.org/stable/c/323d252a4a378834e4fe68298ca61cfc5dd3a460nvd
- git.kernel.org/stable/c/5ec0debbcfd43596e32c1239e993de06a704e04cnvd
- git.kernel.org/stable/c/85311a585a26640760cd0f3349ab9f2905691044nvd
News mentions
0No linked articles in our index yet.