Skip to content

Add advisories: use-after-free via leaked async transfer future (zynq7000-hal, vorago-shared-hal, vorago-shared-periphs, axi-uartlite, axi-uart16550)#2965

Open
kbhetrr wants to merge 2 commits into
rustsec:mainfrom
kbhetrr:async-forget-uaf-advisories
Open

Add advisories: use-after-free via leaked async transfer future (zynq7000-hal, vorago-shared-hal, vorago-shared-periphs, axi-uartlite, axi-uart16550)#2965
kbhetrr wants to merge 2 commits into
rustsec:mainfrom
kbhetrr:async-forget-uaf-advisories

Conversation

@kbhetrr

@kbhetrr kbhetrr commented Jun 13, 2026

Copy link
Copy Markdown

Affected crate(s)

A single soundness issue shared across five embedded async HAL crates by the same
maintainer (Robin Mueller, IRS / University of Stuttgart). They share a
raw-slice/raw-buffer lifetime-erasure pattern.

  • zynq7000-hal 0.1.1 (SPI RX memory corruption + UART TX info disclosure)
  • vorago-shared-hal 0.4.0 (UART TX info disclosure + SPI RX memory corruption)
  • vorago-shared-periphs 0.1.0 (UART TX info disclosure)
  • axi-uartlite <0.2.0 (UART TX info disclosure; patched in 0.2.0)
  • axi-uart16550 <0.2.0 (UART TX info disclosure; patched in 0.2.0)

Links to upstream issue(s) or PR(s)

Reported privately to the maintainer by email on 2026-06-02; coordinated disclosure,
maintainer acknowledged and agreed to informational = "unsound" advisories.

Upstream fixes (mark the async buffer-taking APIs unsafe, add explicit buffer
lifetimes, document the leak hazard):

Severity

informational = "unsound" (soundness issue, not an exploitable CVE). Each crate exposes
a safe async I/O API (embedded_io_async::Write, embedded_hal_async::SpiBus) that
accepts a non-'static borrowed buffer and erases its lifetime to hand a bare pointer to a
peripheral interrupt. Cancellation safety relies entirely on the returned future's Drop.
Because core::mem::forget (and other leaks: Rc/arena cycles, leaked tasks, Box::leak)
is safe and skips Drop, safe code can leak an in-flight transfer future after its borrowed
buffer is freed/reused — the peripheral then reads freed memory (info disclosure, UART TX) or
writes into it (memory corruption, SPI RX), with no unsafe at the call site. Neither the
borrow checker nor Miri observes it (the access is outside the abstract machine and the
lifetime is erased). Severity is application-dependent; trigger is not necessarily
attacker-controlled.

Checklist

  • Advisory filename(s) starts with RUSTSEC-0000-0000 as the ID
  • date field is set to the public disclosure date
  • Contains a concise and descriptive title after advisory metadata
  • Asked maintainer(s) if publishing an advisory is appropriate

@djc

djc commented Jun 13, 2026

Copy link
Copy Markdown
Member

If the releases for the other crates are imminent, I'd prefer to wait to issue their advisories when they are released. Maybe drop them from this PR for now?

@kbhetrr

kbhetrr commented Jun 13, 2026

Copy link
Copy Markdown
Author

@djc Sounds good, thanks for the quick review. I've dropped zynq7000-hal, vorago-shared-hal, and vorago-shared-periphs from this PR — their fixes are merged upstream but not yet released, so I'll open a follow-up PR once the releases are out. This PR now contains only axi-uartlite and axi-uart16550, which are already fixed in the released 0.2.0.

@djc

djc commented Jun 13, 2026

Copy link
Copy Markdown
Member

I'm still on the fence about whether this requires an advisory? Seems pretty hard to reach by accident?

@kbhetrr

kbhetrr commented Jun 14, 2026

Copy link
Copy Markdown
Author

@djc Fair question! The bar for an advisory here isn't likelihood — it's that safe code can trigger UB. These async methods erase a borrowed buffer's lifetime into a raw slice the DMA keeps using, and lean entirely on the future's Drop to stop it. But leaking is safe (mem::forget, ManuallyDrop, Box::leak…), so a caller with no unsafe can drop the borrow while the transfer is live → the engine writes freed stack memory.

Relying on a destructor for memory safety is exactly the unsound pattern that got std::thread::scoped pulled from std (the original "leakpocalypse"); the sound fix is an owned/'static buffer (cf. embedded-dma).

Agreed the likelihood is low.

@robamu

robamu commented Jun 15, 2026

Copy link
Copy Markdown

Seems pretty hard to reach by accident?

Have to agree to this. The primary issue is that safe code can trigger UB like mentioned by @kbhetrr .
The fix to use 'static/owned buffers everywhere is not feasible for some APIs in my opinion because it would prevent using stack allocated buffers.

Whether a corner case like this really warrants putting unsafe functions all over the place is a different question and I am still considering whether I really want to do that.
Other crates have opted to actually not do this and instead only document that you are not allowed to use core::mem::forget on some data structures if the buffers are not 'static.

A clean solution to this would be some way to guarantee that a destructor will run for certain data structures. Rust does not allow this, but I think there is some work going on which might allow this in the future.

@djc

djc commented Jun 15, 2026

Copy link
Copy Markdown
Member

Don't get me wrong, I do feel like unsound code should be fixed, and if making API unsafe is the only way to do so than that's what you should do. But per discussion in

I don't think unsoundness that is hard to reach by accident is worth publishing an advisory for necessarily.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants