Bug 2550678
| Summary: | CVE-2026-110036 device-mapper-persistent-data: device-mapper-persistent-data: Denial of Service via crafted block indices [fedora-all] | ||
|---|---|---|---|
| Product: | [Fedora] Fedora | Reporter: | Marco Benatto <mbenatto> |
| Component: | device-mapper-persistent-data | Assignee: | Marian Csontos <mcsontos> |
| Status: | NEW --- | QA Contact: | Fedora Extras Quality Assurance <extras-qa> |
| Severity: | low | Docs Contact: | |
| Priority: | low | ||
| Version: | rawhide | CC: | agk, gmazyland, lvm-team, mcsontos, prajnoha |
| Target Milestone: | --- | Keywords: | Security, SecurityTracking |
| Target Release: | --- | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Whiteboard: | {"flaws": ["c798d640-e46e-44fe-93da-07b8300e5cb3"]} | ||
| Fixed In Version: | Doc Type: | --- | |
| Doc Text: | Story Points: | --- | |
| Clone Of: | Environment: | ||
| Last Closed: | Type: | --- | |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
| Embargoed: | |||
| Bug Depends On: | |||
| Bug Blocks: | 2474403 | ||
Disclaimer: Community trackers are created by Red Hat Product Security team on a best effort basis. Package maintainers are required to ascertain if the flaw indeed affects their package, before starting the update process. AI_ONLY_REPORT package: device-mapper-persistent-data-1.3.0-1.el10 ------ Summary: Reachable DoS in sync read path from `u64::MAX` block index (`find_runs` overflow -> invalid run -> panic): a crafted metadata-derived block index can terminate affected tooling by reaching an empty vectored read in the synchronous batching path. Requirements to exploit: An attacker needs to supply, or induce a user to process, crafted metadata or an equivalent block list containing `u64::MAX` so that the package reaches the synchronous `read_many_` batching path. This is a local, input-driven denial of service; no privilege escalation, confidentiality impact, or code execution is demonstrated. Component affected: `device-mapper-persistent-data`, specifically the synchronous read path through `src/io_engine/gaps.rs` (`find_runs`), `src/io_engine/sync.rs` (`SyncIoEngine::read_many_`), and `src/io_engine/utils.rs` (`VectoredBlockIo::read_blocks`). Version affected: `device-mapper-persistent-data-1.3.0-1.el10` Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H - 5.5 (MEDIUM) AV:L - the attacker needs local access to provide crafted metadata or a malicious image to the affected tooling. AC:L - the trigger condition is a single malformed block index reaching an otherwise normal batching path. PR:N - no privileges in the vulnerable component are required beyond supplying the crafted input. UI:R - a user or automation must invoke an affected tool on the crafted input. S:U - the impact is limited to the vulnerable process. C:N - no confidentiality impact is demonstrated. I:N - no integrity impact is demonstrated. A:H - the process can panic and terminate, preventing the requested operation from completing. Impact: Moderate. Under Red Hat's severity guidance, this is a real availability issue in a reachable code path, but it does not rise to Important or Critical because the demonstrated attack is local and input-driven rather than a remote service compromise or system compromise. It is more than Low because successful exploitation directly terminates the affected process once the vulnerable path is reached. Embargo: no Reason: this is a local, crafted-input denial of service with straightforward mitigation and no demonstrated confidentiality, integrity, privilege, or code-execution impact. Acknowledgement: Aisle Research Vulnerability Details: The synchronous batched-read path constructs end-exclusive runs with unchecked `+ 1` arithmetic in `src/io_engine/gaps.rs`: ```rust for b in blocks { if let Some((begin, end)) = last { #[allow(clippy::comparison_chain)] if *b > end { let len = b - end; if len > gap_threshold { runs.push(Run(begin, end)); last = Some((*b, *b + 1)); } else { runs.push(Run(begin, end)); runs.push(Gap(end, *b)); last = Some((*b, *b + 1)); } } else if *b == end { last = Some((begin, b + 1)); } else { runs.push(Run(begin, end)); last = Some((*b, b + 1)); } } else { last = Some((*b, b + 1)); } } ``` When `b == u64::MAX`, release builds can wrap this into an invalid end-exclusive range such as `Run(u64::MAX, 0)`. In `SyncIoEngine::read_many_`, `RunOp::Run(b, e)` populates buffers by iterating `for b in *b..*e`; for `Run(u64::MAX, 0)`, that range is empty, so no buffers are pushed even though the batch start is still tracked from the run start. The later vectored read sink in `src/io_engine/utils.rs` then dereferences the first buffer unconditionally: ```rust fn read_blocks(&self, buffers: &mut [&mut [u8]], mut pos: u64) -> Result<Vec<Result<()>>> { let block_size = buffers[0].len(); ``` This produces a process-terminating panic. The available evidence supports reachability through real metadata-derived block lists passed into `read_many`, although not every caller or I/O engine is shown to behave identically. This report establishes the synchronous path. In non-release builds, the same input may instead panic earlier on integer overflow, but the availability impact remains a crash. Steps to reproduce: 1. Add the following regression test to `src/io_engine/sync/tests.rs`: ```rust #[test] #[should_panic] fn panic_on_max_block_index() { let mut v = MockVio::new(); let _ = SyncIoEngine::read_many_(v.into(), &[u64::MAX]).unwrap(); } ``` 2. Run: ```bash cargo test --release panic_on_max_block_index – --nocapture ``` 3. Observe a panic originating from `src/io_engine/utils.rs` when `VectoredBlockIo::read_blocks` indexes `buffers[0]`. Without `--release`, the same input may panic earlier on the unchecked overflow in `find_runs`, which still demonstrates a denial of service. Mitigation: Until a fix is applied, avoid processing untrusted metadata with affected builds, and reject block indices equal to `u64::MAX` or otherwise outside the valid device block range before they reach `generate_runs` or `read_many_`. Caller-side validation is a useful short-term defense, but it should not replace fixing the underlying arithmetic and empty-buffer handling. Proposed Fix: A minimal fix is to use checked arithmetic when constructing end-exclusive runs and to return early from empty vectored reads. ```diff diff --git a/src/io_engine/gaps.rs b/src/io_engine/gaps.rs @@ fn find_runs(blocks: &[u64], gap_threshold: u64) -> Vec<RunOp> { @@ for b in blocks { + let next = match b.checked_add(1) { + Some => n, + None => { + if let Some((begin, end)) = last.take() { + runs.push(Run(begin, end)); + } + continue; + } + }; if let Some((begin, end)) = last { @@ last = Some((*b, *b + 1)); + last = Some((*b, next)); @@ last = Some((*b, *b + 1)); + last = Some((*b, next)); @@ last = Some((begin, b + 1)); + last = Some((begin, next)); @@ last = Some((*b, b + 1)); + last = Some((*b, next)); } } else { last = Some((*b, b + 1)); + last = Some((*b, next)); } } diff --git a/src/io_engine/utils.rs b/src/io_engine/utils.rs @@ fn read_blocks(&self, buffers: &mut [&mut [u8]], mut pos: u64) -> Result<Vec<Result<()>>> { + if buffers.is_empty() { + return Ok(Vec::new()); + } let block_size = buffers[0].len(); ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use