Skip to content

Commit 8ee1e7f

Browse files
authored
Don't use openat2 or statx on Android. (#312)
Some Android versions disallow these even on Linux kernel versions which otherwise would support them, and they crash the process rather than returning `ENOSYS` so there's no way to detect this. So disallow using `statx` and `openat2` on Android altogether.
1 parent 5f5e3b2 commit 8ee1e7f

File tree

3 files changed

+30
-81
lines changed

3 files changed

+30
-81
lines changed

cap-primitives/src/rustix/fs/metadata_ext.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use crate::fs::{ImplFileTypeExt, Metadata, PermissionsExt};
44
use crate::time::{Duration, SystemClock, SystemTime};
5-
#[cfg(any(target_os = "android", target_os = "linux"))]
5+
#[cfg(target_os = "linux")]
66
use rustix::fs::{makedev, Statx, StatxFlags};
77
use rustix::fs::{RawMode, Stat};
88
use std::convert::{TryFrom, TryInto};
@@ -219,7 +219,7 @@ impl MetadataExt {
219219
}
220220

221221
/// Constructs a new instance of `Metadata` from the given `Statx`.
222-
#[cfg(any(target_os = "android", target_os = "linux"))]
222+
#[cfg(target_os = "linux")]
223223
#[inline]
224224
pub(crate) fn from_rustix_statx(statx: Statx) -> Metadata {
225225
Metadata {

cap-primitives/src/rustix/fs/stat_unchecked.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use rustix::fs::{statat, AtFlags};
33
use std::path::Path;
44
use std::{fs, io};
55

6-
#[cfg(any(target_os = "android", target_os = "linux"))]
6+
#[cfg(target_os = "linux")]
77
use rustix::fs::{statx, StatxFlags};
8-
#[cfg(any(target_os = "android", target_os = "linux"))]
8+
#[cfg(target_os = "linux")]
99
use std::sync::atomic::{AtomicU8, Ordering};
1010

1111
/// *Unsandboxed* function similar to `stat`, but which does not perform
@@ -20,18 +20,24 @@ pub(crate) fn stat_unchecked(
2020
FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW,
2121
};
2222

23-
// `statx` is preferred on Linux because it can return creation times.
24-
// Linux kernels prior to 4.11 don't have `statx` and return `ENOSYS`.
25-
// Older versions of Docker/seccomp would return `EPERM` for `statx`; see
26-
// <https://github.com/rust-lang/rust/pull/65685/>. We store the
27-
// availability in a global to avoid unnecessary syscalls.
28-
#[cfg(any(target_os = "android", target_os = "linux"))]
23+
// `statx` is preferred on regular Linux because it can return creation
24+
// times. Linux kernels prior to 4.11 don't have `statx` and return
25+
// `ENOSYS`. Older versions of Docker/seccomp would return `EPERM` for
26+
// `statx`; see <https://github.com/rust-lang/rust/pull/65685/>. We store
27+
// the availability in a global to avoid unnecessary syscalls.
28+
//
29+
// On Android, the [seccomp policy] prevents us from even
30+
// detecting whether `statx` is supported, so don't even try.
31+
//
32+
// [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html
33+
#[cfg(target_os = "linux")]
2934
{
3035
// 0: Unknown
3136
// 1: Not available
3237
// 2: Available
3338
static STATX_STATE: AtomicU8 = AtomicU8::new(0);
3439
let state = STATX_STATE.load(Ordering::Relaxed);
40+
3541
if state != 1 {
3642
let statx_result = statx(
3743
start,

cap-primitives/src/rustix/linux/fs/open_impl.rs

Lines changed: 14 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,24 @@ pub(crate) fn open_impl(
2525
path: &Path,
2626
options: &OpenOptions,
2727
) -> io::Result<fs::File> {
28-
let result = open_beneath(start, path, options);
28+
// On regular Linux, attempt to use `openat2` to accelerate sandboxed
29+
// lookups. On Android, the [seccomp policy] prevents us from even
30+
// detecting whether `openat2` is supported, so don't even try.
31+
//
32+
// [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html
33+
#[cfg(target_os = "linux")]
34+
{
35+
let result = open_beneath(start, path, options);
2936

30-
// If that returned `ENOSYS`, use a fallback strategy.
31-
if let Err(err) = &result {
32-
if Some(rustix::io::Errno::NOSYS.raw_os_error()) == err.raw_os_error() {
33-
return manually::open(start, path, options);
37+
// If we got anything other than a `ENOSYS` error, that's our result.
38+
match result {
39+
Err(err) if err.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {}
40+
Err(err) => return Err(err.into()),
41+
Ok(fd) => return Ok(fd),
3442
}
3543
}
3644

37-
result
45+
manually::open(start, path, options)
3846
}
3947

4048
/// Call the `openat2` system call with `RESOLVE_BENEATH`. If the syscall is
@@ -61,23 +69,6 @@ pub(crate) fn open_beneath(
6169
Mode::empty()
6270
};
6371

64-
// On Android, seccomp kills processes that execute unrecognized system
65-
// calls, so we do an explicit version check rather than relying on
66-
// getting an `ENOSYS`.
67-
#[cfg(target_os = "android")]
68-
{
69-
static CHECKED: AtomicBool = AtomicBool::new(false);
70-
71-
if !CHECKED.load(Relaxed) {
72-
if !openat2_supported() {
73-
INVALID.store(true, Relaxed);
74-
return Err(rustix::io::Errno::NOSYS.into());
75-
}
76-
77-
CHECKED.store(true, Relaxed);
78-
}
79-
}
80-
8172
// We know `openat2` needs a `&CStr` internally; to avoid allocating on
8273
// each iteration of the loop below, allocate the `CString` now.
8374
path.into_with_c_str(|path_c_str| {
@@ -135,54 +126,6 @@ pub(crate) fn open_beneath(
135126
})
136127
}
137128

138-
/// Test whether `openat2` is supported on the currently running OS.
139-
#[cfg(target_os = "android")]
140-
fn openat2_supported() -> bool {
141-
// `openat2` is supported in Linux 5.6 and later. Parse the current
142-
// Linux version from the `release` field from `uname` to detect this.
143-
let uname = rustix::process::uname();
144-
let release = uname.release().to_bytes();
145-
if let Some((major, minor)) = linux_major_minor(release) {
146-
if major >= 6 || (major == 5 && minor >= 6) {
147-
return true;
148-
}
149-
}
150-
151-
false
152-
}
153-
154-
/// Extract the major and minor values from a Linux `release` string.
155-
#[cfg(target_os = "android")]
156-
fn linux_major_minor(release: &[u8]) -> Option<(u32, u32)> {
157-
let mut parts = release.split(|b| *b == b'.');
158-
if let Some(major) = parts.next() {
159-
if let Ok(major) = std::str::from_utf8(major) {
160-
if let Ok(major) = major.parse::<u32>() {
161-
if let Some(minor) = parts.next() {
162-
if let Ok(minor) = std::str::from_utf8(minor) {
163-
if let Ok(minor) = minor.parse::<u32>() {
164-
return Some((major, minor));
165-
}
166-
}
167-
}
168-
}
169-
}
170-
}
171-
172-
None
173-
}
174-
175-
#[cfg(target_os = "android")]
176-
#[test]
177-
fn test_linux_major_minor() {
178-
assert_eq!(linux_major_minor(b"5.11.0-5489-something"), Some((5, 11)));
179-
assert_eq!(linux_major_minor(b"5.10.0-9-whatever"), Some((5, 10)));
180-
assert_eq!(linux_major_minor(b"5.6.0"), Some((5, 6)));
181-
assert_eq!(linux_major_minor(b"2.6.34"), Some((2, 6)));
182-
assert_eq!(linux_major_minor(b""), None);
183-
assert_eq!(linux_major_minor(b"linux-2.6.32"), None);
184-
}
185-
186129
#[cfg(racy_asserts)]
187130
fn check_open(start: &fs::File, path: &Path, options: &OpenOptions, file: &fs::File) {
188131
let check = manually::open(

0 commit comments

Comments
 (0)