Skip to content

Commit 04e1cad

Browse files
committed
Allow read_link to read absolute paths
Previously it would return an error if the link's destination were absolute. But there were three problems with this: * It wouldn't return an error if the link's destination were a relative path that uses ../ to point outside of the sandbox. That's inconsistent. * Sometimes a symlink with an absolute path doesn't escape the sandbox. For example /sandbox/link -> /sandbox/target. But read_link wouldn't allow that. * More importantly, sometimes it's important to read a link that points outside of the sandbox. For example, a backup application could copy all of the files from the root directory of one computer to a subdirectory of another. Symlinks would be broken, but would work again after a restore operation. The application that performs the restore would need to be able to read link targets, even if they point outside of the root. Or, a jail file system could contain absolute symlinks that resolve correctly for a jailed process, but not for an unjailed one. But it would still sometimes be useful for an unjailed process to read the links. Fixes #353
1 parent 8f2f60f commit 04e1cad

File tree

3 files changed

+13
-45
lines changed

3 files changed

+13
-45
lines changed

cap-primitives/src/fs/read_link.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This defines `read_link`, the primary entrypoint to sandboxed symbolic link
22
//! dereferencing.
33
4-
use crate::fs::{errors, read_link_impl};
4+
use crate::fs::read_link_impl;
55
#[cfg(racy_asserts)]
66
use crate::fs::{map_result, read_link_unchecked, stat, FollowSymlinks};
77
use std::path::{Path, PathBuf};
@@ -24,27 +24,12 @@ pub fn read_link_contents(start: &fs::File, path: &Path) -> io::Result<PathBuf>
2424
result
2525
}
2626

27-
/// Perform a `readlinkat`-like operation, ensuring that the resolution of the
28-
/// path never escapes the directory tree rooted at `start`, and also verifies
29-
/// that the link target is not absolute.
27+
/// Perform a `readlinkat`-like operation.
3028
#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
3129
#[inline]
3230
pub fn read_link(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
3331
// Call the underlying implementation.
34-
let result = read_link_contents(start, path);
35-
36-
// Don't allow reading symlinks to absolute paths. This isn't strictly
37-
// necessary to preserve the sandbox, since `open` will refuse to follow
38-
// absolute paths in any case. However, it is useful to enforce this
39-
// restriction to avoid leaking information about the host filesystem
40-
// outside the sandbox.
41-
if let Ok(path) = &result {
42-
if path.has_root() {
43-
return Err(errors::escape_attempt());
44-
}
45-
}
46-
47-
result
32+
read_link_contents(start, path)
4833
}
4934

5035
#[cfg(racy_asserts)]

tests/fs.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -935,24 +935,6 @@ fn symlink_noexist() {
935935

936936
#[test]
937937
fn read_link() {
938-
if cfg!(windows) {
939-
// directory symlink
940-
let root = Dir::open_ambient_dir(r"C:\", ambient_authority()).unwrap();
941-
error_contains!(
942-
root.read_link(r"Users\All Users"),
943-
"a path led outside of the filesystem"
944-
);
945-
// junction
946-
error_contains!(
947-
root.read_link(r"Users\Default User"),
948-
"a path led outside of the filesystem"
949-
);
950-
// junction with special permissions
951-
error_contains!(
952-
root.read_link(r"Documents and Settings\"),
953-
"a path led outside of the filesystem"
954-
);
955-
}
956938
let tmpdir = tmpdir();
957939
let link = "link";
958940
if !got_symlink_permission(&tmpdir) {

tests/symlinks.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod sys_common;
44
use cap_fs_ext::DirExt;
55
use cap_std::ambient_authority;
66
use cap_std::fs::Dir;
7+
use std::path::Path;
78
use sys_common::io::tmpdir;
89
use sys_common::symlink_supported;
910

@@ -116,19 +117,19 @@ fn readlink_absolute() {
116117
let tmpdir = check!(Dir::open_ambient_dir(dir.path(), ambient_authority()));
117118

118119
#[cfg(not(windows))]
119-
error_contains!(
120-
tmpdir.read_link("thing_symlink"),
121-
"a path led outside of the filesystem"
120+
assert_eq!(
121+
tmpdir.read_link("thing_symlink").unwrap(),
122+
Path::new("/thing")
122123
);
123124
#[cfg(windows)]
124-
error_contains!(
125-
tmpdir.read_link("file_symlink_file"),
126-
"a path led outside of the filesystem"
125+
assert_eq!(
126+
tmpdir.read_link("file_symlink_file").unwrap(),
127+
Path::new("/file")
127128
);
128129
#[cfg(windows)]
129-
error_contains!(
130-
tmpdir.read_link("dir_symlink_dir"),
131-
"a path led outside of the filesystem"
130+
assert_eq!(
131+
tmpdir.read_link("dir_symlink_dir").unwrap(),
132+
Path::new("/dir")
132133
);
133134
}
134135

0 commit comments

Comments
 (0)