Bug 2474403 (CVE-2026-110036)
| Summary: | CVE-2026-110036 device-mapper-persistent-data: device-mapper-persistent-data: Denial of Service via crafted block indices | ||
|---|---|---|---|
| Product: | [Other] Security Response | Reporter: | OSIDB Bzimport <bzimport> |
| Component: | vulnerability | Assignee: | Product Security <prodsec-ir-bot> |
| Status: | NEW --- | QA Contact: | |
| Severity: | low | Docs Contact: | |
| Priority: | low | ||
| Version: | unspecified | CC: | akhatavk, aos-team-art-private, asdas, dpaolell, jdelft, jupierce, lgarciaa, mbiarnes, ppalepu, ppostler, prdhamdh, rhel-process-autobot, security-response-team, sghai, sidsharm, suppawar, vlaad, watson-tool-maintainers |
| Target Milestone: | --- | Keywords: | Security |
| Target Release: | --- | ||
| Hardware: | All | ||
| OS: | Linux | ||
| Whiteboard: | |||
| Fixed In Version: | Doc Type: | --- | |
| Doc Text: |
A flaw was found in device-mapper-persistent-data. A local attacker can cause a Denial of Service (DoS) by inducing a user or system tool to process crafted device metadata containing an excessively large block index. An arithmetic error in the synchronous block reading logic causes the application to panic and unexpectedly terminate.
|
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: | 2550678 | ||
| Bug Blocks: | |||
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