Skip to content

Various Fixes of Potential Undefined Behavior (LLM Assisted) (5/N) - #2083

Merged
phip1611 merged 5 commits into
mainfrom
ub-fixes-7
Sep 14, 2026
Merged

phip1611 merged 5 commits into
mainfrom
ub-fixes-7

Conversation

@phip1611

Copy link
Copy Markdown
Member

Checklist

  • Sensible git history (for example, squash "typo" or "fix" commits). See the Rewriting History guide for help.
  • Update the changelog (if necessary)

`SimpleNetwork::mode` returns a reference into the mode structure owned
by the firmware, while every method that calls into the firmware took
`&self`. Start, Stop, Initialize, Shutdown, ReceiveFilters,
StationAddress and GetStatus are specified to update that structure, so
a caller could hold the reference from `mode` across a call that
overwrites the memory behind it. That is undefined behavior, and Miri
rejects it under both aliasing models.

Take `&mut self` in these methods, plus `reset`, which reinitializes
the adapter. The borrow checker then rejects holding the mode reference
across them. `transmit`, `receive`, the statistics and NVRAM accessors
and `mcast_ip_to_mac` do not update the mode and keep `&self`.

Found with Miri using a mocked firmware.
`Shell::current_dir` and `Shell::var` return references to strings that
the shell owns, while `set_current_dir` and `set_var` took `&self`. The
EDK2 shell frees and reallocates that storage when the value changes,
so a string obtained before the setter dangled afterwards while still
being usable from safe code.

Take `&mut self` in the two setters so that the borrow checker rejects
holding such a string across them. This also covers the `Vars` iterator,
which borrows the shell for as long as it is alive.

Found by inspection of the EDK2 shell implementation.
The specification declares `This` as an input parameter of
`EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.QueryMode()`, and the implementations
in EDK2 only read from it. The `uefi` wrapper nevertheless had to cast
the shared reference behind `Output::current_mode` and `Output::modes`
to `*mut` to satisfy the function pointer type. Writing through such a
pointer would be undefined behavior, so the type should not invite it.

Declare the parameter as `*const Self` and pass the shared reference
directly, as was done for `SimplePointerProtocol::get_state` in commit
3078571.
`with_stdin`, `with_stdout` and `with_stderr` create an exclusive
reference to the protocol behind the raw pointer in the system table
for the duration of the closure. A nested call, which is easy to make
by accident through `println!` or the `log` macros from inside a
`with_stdout` closure, creates a second exclusive reference to the same
protocol while the first one is still in use. That is undefined
behavior under the Stacked Borrows model, and Miri reports it.

Preventing this at runtime would need a reentrancy guard on every
call. Document the requirement instead.
`CStr8::from_ptr`, `CStr16::from_ptr` and `DevicePath::from_ffi_ptr`
determine the length of the data by scanning for the NUL character or
the end-entire node. Nothing bounds that scan, because the firmware
interfaces that hand out such pointers do not report a length. The
safety sections did not say that a missing terminator is undefined
behavior rather than an error, nor that the memory must stay unchanged
for the returned lifetime.

Spell out both. `DevicePathNode::from_ffi_ptr` reads the length from
the node header, so it needs readable memory for that many bytes.
@phip1611
phip1611 enabled auto-merge September 14, 2026 08:03
@phip1611
phip1611 added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 4dceebd Sep 14, 2026
19 of 20 checks passed
@phip1611
phip1611 deleted the ub-fixes-7 branch September 14, 2026 08:08
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.

1 participant