Skip to content

Commit 4653d86

Browse files
committed
Add Dir::{read_link_contents,symlink_contents} methods
These allow creating and readling symlinks whose target may be an absolute path. There is no security risk because security is as always provided by the OS; the OS's openat implementation will refuse to follow a symlink whose target is an absolute path. Fixes #354
1 parent 8f2f60f commit 4653d86

File tree

4 files changed

+147
-5
lines changed

4 files changed

+147
-5
lines changed

cap-std/src/fs/dir.rs

+40-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use crate::os::unix::net::{UnixDatagram, UnixListener, UnixStream};
77
use cap_primitives::fs::set_permissions;
88
use cap_primitives::fs::{
99
canonicalize, copy, create_dir, hard_link, open, open_ambient_dir, open_dir, open_parent_dir,
10-
read_base_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, remove_open_dir,
11-
remove_open_dir_all, rename, stat, DirOptions, FollowSymlinks, Permissions,
10+
read_base_dir, read_dir, read_link, read_link_contents, remove_dir, remove_dir_all,
11+
remove_file, remove_open_dir, remove_open_dir_all, rename, stat, DirOptions, FollowSymlinks,
12+
Permissions,
1213
};
1314
use cap_primitives::AmbientAuthority;
1415
use io_lifetimes::AsFilelike;
@@ -21,7 +22,7 @@ use std::path::{Path, PathBuf};
2122
use std::{fmt, fs};
2223
#[cfg(not(windows))]
2324
use {
24-
cap_primitives::fs::symlink,
25+
cap_primitives::fs::{symlink, symlink_contents},
2526
io_extras::os::rustix::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
2627
};
2728
#[cfg(windows)]
@@ -284,12 +285,22 @@ impl Dir {
284285
/// Reads a symbolic link, returning the file that the link points to.
285286
///
286287
/// This corresponds to [`std::fs::read_link`], but only accesses paths
287-
/// relative to `self`.
288+
/// relative to `self`. Unlike [`read_link_contents`], this method considers it an error if
289+
/// the link's target is an absolute path.
288290
#[inline]
289291
pub fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
290292
read_link(&self.std_file, path.as_ref())
291293
}
292294

295+
/// Reads a symbolic link, returning the file that the link points to.
296+
///
297+
/// This corresponds to [`std::fs::read_link`]. but only accesses paths
298+
/// relative to `self`.
299+
#[inline]
300+
pub fn read_link_contents<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
301+
read_link_contents(&self.std_file, path.as_ref())
302+
}
303+
293304
/// Read the entire contents of a file into a string.
294305
///
295306
/// This corresponds to [`std::fs::read_to_string`], but only accesses
@@ -411,13 +422,38 @@ impl Dir {
411422
/// This corresponds to [`std::os::unix::fs::symlink`], but only accesses
412423
/// paths relative to `self`.
413424
///
425+
/// Unlike [`symlink_contents`] this method will return an error if `original` is an absolute
426+
/// path.
427+
///
414428
/// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html
415429
#[cfg(not(windows))]
416430
#[inline]
417431
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
418432
symlink(original.as_ref(), &self.std_file, link.as_ref())
419433
}
420434

435+
/// Creates a new symbolic link on a filesystem.
436+
///
437+
/// The `original` argument provides the target of the symlink. The `link`
438+
/// argument provides the name of the created symlink.
439+
///
440+
/// Despite the argument ordering, `original` is not resolved relative to
441+
/// `self` here. `link` is resolved relative to `self`, and `original` is
442+
/// not resolved within this function.
443+
///
444+
/// The `link` path is resolved when the symlink is dereferenced, relative
445+
/// to the directory that contains it.
446+
///
447+
/// This corresponds to [`std::os::unix::fs::symlink`], but only accesses
448+
/// paths relative to `self`.
449+
///
450+
/// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html
451+
#[cfg(not(windows))]
452+
#[inline]
453+
pub fn symlink_contents<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
454+
symlink_contents(original.as_ref(), &self.std_file, link.as_ref())
455+
}
456+
421457
/// Creates a new file symbolic link on a filesystem.
422458
///
423459
/// The `original` argument provides the target of the symlink. The `link`

cap-std/src/fs_utf8/dir.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,24 @@ impl Dir {
230230
/// Reads a symbolic link, returning the file that the link points to.
231231
///
232232
/// This corresponds to [`std::fs::read_link`], but only accesses paths
233-
/// relative to `self`.
233+
/// relative to `self`. Unlike [`read_link_contents`], this method considers it an error if
234+
/// the link's target is an absolute path.
234235
#[inline]
235236
pub fn read_link<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<Utf8PathBuf> {
236237
let path = from_utf8(path.as_ref())?;
237238
self.cap_std.read_link(path).and_then(to_utf8)
238239
}
239240

241+
/// Reads a symbolic link, returning the file that the link points to.
242+
///
243+
/// This corresponds to [`std::fs::read_link`]. but only accesses paths
244+
/// relative to `self`.
245+
#[inline]
246+
pub fn read_link_contents<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<Utf8PathBuf> {
247+
let path = from_utf8(path.as_ref())?;
248+
self.cap_std.read_link_contents(path).and_then(to_utf8)
249+
}
250+
240251
/// Read the entire contents of a file into a string.
241252
///
242253
/// This corresponds to [`std::fs::read_to_string`], but only accesses
@@ -371,6 +382,9 @@ impl Dir {
371382
/// This corresponds to [`std::os::unix::fs::symlink`], but only accesses
372383
/// paths relative to `self`.
373384
///
385+
/// Unlike [`symlink_contents`] this method will return an error if `original` is an absolute
386+
/// path.
387+
///
374388
/// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html
375389
#[cfg(not(windows))]
376390
#[inline]
@@ -384,6 +398,34 @@ impl Dir {
384398
self.cap_std.symlink(original, link)
385399
}
386400

401+
/// Creates a new symbolic link on a filesystem.
402+
///
403+
/// The `original` argument provides the target of the symlink. The `link`
404+
/// argument provides the name of the created symlink.
405+
///
406+
/// Despite the argument ordering, `original` is not resolved relative to
407+
/// `self` here. `link` is resolved relative to `self`, and `original` is
408+
/// not resolved within this function.
409+
///
410+
/// The `link` path is resolved when the symlink is dereferenced, relative
411+
/// to the directory that contains it.
412+
///
413+
/// This corresponds to [`std::os::unix::fs::symlink`], but only accesses
414+
/// paths relative to `self`.
415+
///
416+
/// [`std::os::unix::fs::symlink`]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html
417+
#[cfg(not(windows))]
418+
#[inline]
419+
pub fn symlink_contents<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
420+
&self,
421+
original: P,
422+
link: Q,
423+
) -> io::Result<()> {
424+
let original = from_utf8(original.as_ref())?;
425+
let link = from_utf8(link.as_ref())?;
426+
self.cap_std.symlink_contents(original, link)
427+
}
428+
387429
/// Creates a new file symbolic link on a filesystem.
388430
///
389431
/// The `original` argument provides the target of the symlink. The `link`

tests/fs.rs

+32
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ use sys_common::symlink_junction;
2929
use rand::rngs::StdRng;
3030
use rand::{RngCore, SeedableRng};
3131

32+
#[cfg(not(windows))]
33+
fn symlink_contents<P: AsRef<Path>, Q: AsRef<Path>>(
34+
src: P,
35+
tmpdir: &TempDir,
36+
dst: Q,
37+
) -> io::Result<()> {
38+
tmpdir.symlink_contents(src, dst)
39+
}
3240
#[cfg(not(windows))]
3341
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> {
3442
tmpdir.symlink(src, dst)
@@ -971,6 +979,30 @@ fn readlink_not_symlink() {
971979
}
972980
}
973981

982+
#[cfg(not(windows))]
983+
#[test]
984+
fn read_link_contents() {
985+
let tmpdir = tmpdir();
986+
let link = "link";
987+
if !got_symlink_permission(&tmpdir) {
988+
return;
989+
};
990+
check!(symlink_file(&"foo", &tmpdir, &link));
991+
assert_eq!(check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), "foo");
992+
}
993+
994+
#[cfg(not(windows))]
995+
#[test]
996+
fn read_link_contents_absolute() {
997+
let tmpdir = tmpdir();
998+
let link = "link";
999+
if !got_symlink_permission(&tmpdir) {
1000+
return;
1001+
};
1002+
check!(symlink_contents(&"/foo", &tmpdir, &link));
1003+
assert_eq!(check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), "/foo");
1004+
}
1005+
9741006
#[test]
9751007
fn links_work() {
9761008
let tmpdir = tmpdir();

tests/fs_utf8.rs

+32
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ use sys_common::symlink_junction_utf8 as symlink_junction;
3030
use rand::rngs::StdRng;
3131
use rand::{RngCore, SeedableRng};
3232

33+
#[cfg(not(windows))]
34+
fn symlink_contents<P: AsRef<Path>, Q: AsRef<Path>>(
35+
src: P,
36+
tmpdir: &TempDir,
37+
dst: Q,
38+
) -> io::Result<()> {
39+
tmpdir.symlink_contents(src, dst)
40+
}
3341
#[cfg(not(windows))]
3442
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> {
3543
tmpdir.symlink(src, dst)
@@ -974,6 +982,30 @@ fn readlink_not_symlink() {
974982
}
975983
}
976984

985+
#[cfg(not(windows))]
986+
#[test]
987+
fn read_link_contents() {
988+
let tmpdir = tmpdir();
989+
let link = "link";
990+
if !got_symlink_permission(&tmpdir) {
991+
return;
992+
};
993+
check!(symlink_file(&"foo", &tmpdir, &link));
994+
assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "foo");
995+
}
996+
997+
#[cfg(not(windows))]
998+
#[test]
999+
fn read_link_contents_absolute() {
1000+
let tmpdir = tmpdir();
1001+
let link = "link";
1002+
if !got_symlink_permission(&tmpdir) {
1003+
return;
1004+
};
1005+
check!(symlink_contents(&"/foo", &tmpdir, &link));
1006+
assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "/foo");
1007+
}
1008+
9771009
#[test]
9781010
fn links_work() {
9791011
let tmpdir = tmpdir();

0 commit comments

Comments
 (0)