Skip to content

Map out-of-range raw OS errors to EINVAL on linux_raw - #1712

Open
stepancheg wants to merge 1 commit into
bytecodealliance:mainfrom
stepancheg:st/errno-einval-1781
Open

stepancheg wants to merge 1 commit into
bytecodealliance:mainfrom
stepancheg:st/errno-einval-1781

Conversation

@stepancheg

Copy link
Copy Markdown

Errno::from_raw_os_error asserted on values outside Linux's 1..4096 range. Return Errno::INVAL instead. This also matches the libc backend, whose from_raw_os_error does not panic.

Error handling usually runs when something is already broken, and it is not the default code path, so it tends to be less exercised. A bad errno can come from various places: an incorrectly implemented syscall, or a syscall wrapper that returns a stray value. Mapping those to EINVAL keeps the error observable without taking the process down.

Assisted-by: Cursor:auto

Comment thread src/backend/linux_raw/io/errno.rs Outdated
Comment thread src/backend/linux_raw/io/errno.rs Outdated
@stepancheg
stepancheg force-pushed the st/errno-einval-1781 branch 2 times, most recently from 67bbf08 to 594e8c9 Compare October 18, 2026 23:10
@stepancheg

Copy link
Copy Markdown
Author

Applied suggestions.

@oech3

oech3 commented Oct 23, 2026

Copy link
Copy Markdown

I think we should not care about kernel's bug at userspace. EINVAL should be used only for actual EINVAL.

@xtqqczze

xtqqczze commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

I think we should not care about kernel's bug at userspace. EINVAL should be used only for actual EINVAL.

In practice, we already use Errno::INVAL everywhere for this purpose:

let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
Some(encoded_speed) => encoded_speed,
#[cfg(linux_kernel)]
None => c::BOTHER,
#[cfg(not(linux_kernel))]
None => return Err(io::Errno::INVAL),

@stepancheg

Copy link
Copy Markdown
Author

Errno::from_raw_os_error is a public function. It can be called from many places, not necessarily from rustix itself returning from kernel: can be third-party library, can be some code incorrectly migrated from other library to rustix.

Panicking can result in outages like the one in Cloudflare https://blog.cloudflare.com/18-november-2025-outage/

@xtqqczze

Copy link
Copy Markdown
Contributor

To be honest, I'm not sure mapping this to EINVAL (Invalid argument) makes any more sense than simply panicking here. Ideally, the return type would be something like Option<Self>, but that would be a breaking change.

@xtqqczze

Copy link
Copy Markdown
Contributor

I think panicking on an unexpected error is more appropriate than silently mapping to a different error.

@xtqqczze

Copy link
Copy Markdown
Contributor

@stepancheg Could you elaborate on why this is an issue in your use case?

@stepancheg

Copy link
Copy Markdown
Author

There are no known issues, it is something I noticed while considering migration from nix to rustix (similar function in nix does not panic).

Our project is large, many servers, a lot of code, much of it is low level (virtualization, FUSE, userspace block devices, etc). There are bugs in our project code (as in any large private project), yet we have strict no panicking policy: code is not allowed to panic unless invariant is local and can be easily verified. Crashing the server is expensive (in particular, graceful shut down of the service takes hours). We must not crash. So far we did not have any panics in production: some code path breaks, error is logged, diagnostics collected, maybe alert is raised, other operations continue.

At least for our case, graceful handling of errors is strictly better than panicking: we see some strange behavior, we investigate at our own pace. Instead of waking up in the middle of the night to deal with cascading failure of the service.

Possible alternative to this PR is having something like

fn from_raw_os_error_opt(raw: i32) -> Option<Errno> { ... }
#[deprecated]
fn from_raw_os_error(raw: i32) -> Errno { ... }

I would argue that it does not buy much: a shared library author should still do .unwrap_or(Errno::EINVAL) and won't panic, just more code for the same result. But it would be acceptable.

@stepancheg

Copy link
Copy Markdown
Author

Just found: Linux kernel similarly does not panic on invalid errno. I guess their motivation is similar to mine:

https://github.com/torvalds/linux/blob/734bef548a96e8774d6eef5dfa2026c6edc4e80b/rust/kernel/error.rs#L224

@xtqqczze

Copy link
Copy Markdown
Contributor

I suppose we could add a non-panicking from_raw_os_error_unchecked constructor:

pub const unsafe fn from_raw_os_error_unchecked(raw: i32) -> Errno

However, this would shift the responsibility for ensuring that raw satisfies the invariants required by Errno onto the caller.

To be honest, if the possibility of a panic is the main concern, consumers should probably just validate the raw value before passing it to from_raw_os_error.

@xtqqczze

xtqqczze commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Just found: Linux kernel similarly does not panic on invalid errno.

Right, the Linux kernel similarly maps out-of-range raw values to EINVAL. That seems reasonable here as well. We could add a debug_assert! to catch invalid values during development.

@stepancheg

Copy link
Copy Markdown
Author

from_raw_os_error_unchecked

unsafe is supposed to be used for for memory violations. Safe panicking code is not supposed to be unsafe according to rust guidelines if I read them correctly.

You are not suggesting for from_raw_os_error_unchecked to UB instead of panicking, are you? Seems unnecessary because this ifs are cheap.

We could add a debug_assert!

Let me update the PR with debug_assert! (later today, AFK now).

@xtqqczze

Copy link
Copy Markdown
Contributor

Yeah, I was suggesting that from_raw_os_error_unchecked would have UB for out-of-range raw values, similar to from_errno_unchecked in the Linux kernel. But I agree that it seems unnecessary if we go with the current approach.

`Errno::from_raw_os_error` asserted on values outside Linux's `1..4096`
range. Return `EINVAL` instead. This also matches the libc backend,
whose `from_raw_os_error` does not panic.

Error handling usually runs when something is already broken, and it is
not the default code path, so it tends to be less exercised. A bad
`errno` can come from various places: an incorrectly implemented
syscall, or a syscall wrapper that returns a stray value. Mapping those
to `EINVAL` keeps the error observable without taking the process down.

Assisted-by: Cursor:auto
@stepancheg
stepancheg force-pushed the st/errno-einval-1781 branch from 594e8c9 to 5977c21 Compare October 23, 2026 20:15
@stepancheg

Copy link
Copy Markdown
Author

Updated the PR to debug_assert! errno range.

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