Skip to content

Commit 54c97de

Browse files
committed
Add support for openat2.
Adds support for openat2 on linux. Includes a new ResolveFlag struct to pass resolve flags, which is passed directly to the new fcntl::openat2 function. libc::open_how isn't exposed here, which may mean this API needs to change if there's an update to openat2 to extend open_how with new fields.
1 parent c6a7d40 commit 54c97de

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/fcntl.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,80 @@ pub fn openat<P: ?Sized + NixPath>(
242242
Errno::result(fd)
243243
}
244244

245+
#[cfg(target_os = "linux")]
246+
libc_bitflags! {
247+
/// Path resolution flags.
248+
pub struct ResolveFlag: libc::c_ulonglong {
249+
/// Do not permit the path resolution to succeed if any component of
250+
/// the resolution is not a descendant of the directory indicated by
251+
/// dirfd. This causes absolute symbolic links (and absolute values of
252+
/// pathname) to be rejected.
253+
RESOLVE_BENEATH;
254+
255+
/// Treat the directory referred to by dirfd as the root directory
256+
/// while resolving pathname.
257+
RESOLVE_IN_ROOT;
258+
259+
/// Disallow all magic-link resolution during path resolution. Magic
260+
/// links are symbolic link-like objects that are most notably found
261+
/// in proc(5); examples include `/proc/[pid]/exe` and `/proc/[pid]/fd/*`.
262+
///
263+
/// See symlink(7) for more details.
264+
RESOLVE_NO_MAGICLINKS;
265+
266+
/// Disallow resolution of symbolic links during path resolution. This
267+
/// option implies RESOLVE_NO_MAGICLINKS.
268+
RESOLVE_NO_SYMLINKS;
269+
270+
/// Disallow traversal of mount points during path resolution (including
271+
/// all bind mounts).
272+
RESOLVE_NO_XDEV;
273+
}
274+
}
275+
276+
/// open or create a file for reading, writing or executing.
277+
///
278+
/// `openat2` is an extension of the [`openat`] function that allows the caller
279+
/// to control how path resolution happens.
280+
///
281+
/// # See also
282+
///
283+
/// [openat2](https://man7.org/linux/man-pages/man2/openat2.2.html)
284+
#[cfg(target_os = "linux")]
285+
pub fn openat2<P: ?Sized + NixPath>(
286+
dirfd: RawFd,
287+
path: &P,
288+
oflag: OFlag,
289+
mode: Mode,
290+
resolve: ResolveFlag,
291+
) -> Result<RawFd> {
292+
let mut how = open_how(oflag, mode, resolve);
293+
let fd = path.with_nix_path(|cstr| unsafe {
294+
libc::syscall(
295+
libc::SYS_openat2,
296+
dirfd,
297+
cstr.as_ptr(),
298+
&mut how as *mut _,
299+
std::mem::size_of::<libc::open_how>(),
300+
)
301+
})?;
302+
Errno::result(fd as i32)
303+
}
304+
305+
#[cfg(target_os = "linux")]
306+
fn open_how(oflag: OFlag, mode: Mode, resolve: ResolveFlag) -> libc::open_how {
307+
use std::ptr::addr_of_mut;
308+
309+
let mut how = std::mem::MaybeUninit::<libc::open_how>::uninit();
310+
let ptr = how.as_mut_ptr();
311+
unsafe {
312+
addr_of_mut!((*ptr).flags).write(oflag.bits() as libc::c_ulonglong);
313+
addr_of_mut!((*ptr).mode).write(mode.bits() as libc::c_ulonglong);
314+
addr_of_mut!((*ptr).resolve).write(resolve.bits());
315+
how.assume_init()
316+
}
317+
}
318+
245319
/// Change the name of a file.
246320
///
247321
/// The `renameat` function is equivalent to `rename` except in the case where either `old_path`

test/test_fcntl.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use nix::errno::*;
44
use nix::fcntl::{open, readlink, OFlag};
55
#[cfg(not(target_os = "redox"))]
66
use nix::fcntl::{openat, readlinkat, renameat};
7+
#[cfg(target_os = "linux")]
8+
use nix::fcntl::{openat2, ResolveFlag};
79
#[cfg(all(
810
target_os = "linux",
911
target_env = "gnu",
@@ -57,6 +59,60 @@ fn test_openat() {
5759
close(dirfd).unwrap();
5860
}
5961

62+
#[test]
63+
#[cfg(target_os = "linux")]
64+
// QEMU does not handle openat well enough to satisfy this test
65+
// https://gitlab.com/qemu-project/qemu/-/issues/829
66+
#[cfg_attr(qemu, ignore)]
67+
fn test_openat2() {
68+
const CONTENTS: &[u8] = b"abcd";
69+
let mut tmp = NamedTempFile::new().unwrap();
70+
tmp.write_all(CONTENTS).unwrap();
71+
72+
let dirfd =
73+
open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
74+
.unwrap();
75+
76+
let fd = openat2(
77+
dirfd,
78+
tmp.path().file_name().unwrap(),
79+
OFlag::O_RDONLY,
80+
Mode::empty(),
81+
ResolveFlag::RESOLVE_BENEATH,
82+
)
83+
.unwrap();
84+
85+
let mut buf = [0u8; 1024];
86+
assert_eq!(4, read(fd, &mut buf).unwrap());
87+
assert_eq!(CONTENTS, &buf[0..4]);
88+
89+
close(fd).unwrap();
90+
close(dirfd).unwrap();
91+
}
92+
93+
#[test]
94+
#[cfg(target_os = "linux")]
95+
fn test_openat2_forbidden() {
96+
let mut tmp = NamedTempFile::new().unwrap();
97+
tmp.write_all(b"let me out").unwrap();
98+
99+
let dirfd =
100+
open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
101+
.unwrap();
102+
103+
let escape_attempt =
104+
tmp.path().parent().unwrap().join("../../../hello.txt");
105+
106+
let res = openat2(
107+
dirfd,
108+
&escape_attempt,
109+
OFlag::O_RDONLY,
110+
Mode::empty(),
111+
ResolveFlag::RESOLVE_BENEATH,
112+
);
113+
assert_eq!(Err(Errno::EXDEV), res);
114+
}
115+
60116
#[test]
61117
#[cfg(not(target_os = "redox"))]
62118
fn test_renameat() {

0 commit comments

Comments
 (0)