Skip to content

Commit 2a2f4b0

Browse files
authored
Fix handling of .. paths in symlinks. (#366)
* Fix handling of `..` paths in symlinks. When `..` appears at the end of a symlink target, but is not at the end of the full path target, don't mark the path as being expected to open a directory. This fixes the reduced testcase in bytecodealliance/wasmtime#9272. * Update CI to macos-12.
1 parent 95e84f4 commit 2a2f4b0

File tree

3 files changed

+331
-18
lines changed

3 files changed

+331
-18
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ jobs:
201201
runs-on: ${{ matrix.os }}
202202
strategy:
203203
matrix:
204-
build: [stable, windows-latest, windows-2019, macos-latest, macos-11, beta, ubuntu-20.04, aarch64-ubuntu]
204+
build: [stable, windows-latest, windows-2019, macos-latest, macos-12, beta, ubuntu-20.04, aarch64-ubuntu]
205205
include:
206206
- build: stable
207207
os: ubuntu-latest

cap-primitives/src/fs/manually/open.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ impl<'start> Context<'start> {
314314

315315
/// Push the components of `destination` onto the worklist stack.
316316
fn push_symlink_destination(&mut self, destination: PathBuf) -> io::Result<()> {
317+
let at_end = self.components.is_empty();
317318
let trailing_slash = path_has_trailing_slash(&destination);
318319
let trailing_dot = path_has_trailing_dot(&destination);
319320
let trailing_dotdot = destination.ends_with(Component::ParentDir);
@@ -362,9 +363,11 @@ impl<'start> Context<'start> {
362363

363364
// Record whether the new components ended with a path that implies
364365
// an open of `.` at the end of path resolution.
365-
self.follow_with_dot |= trailing_dot | trailing_dotdot;
366-
self.trailing_slash |= trailing_slash;
367-
self.dir_required |= trailing_slash;
366+
if at_end {
367+
self.follow_with_dot |= trailing_dot | trailing_dotdot;
368+
self.trailing_slash |= trailing_slash;
369+
self.dir_required |= trailing_slash;
370+
}
368371

369372
// As an optimization, hold onto the `PathBuf` buffer for later reuse.
370373
self.reuse = destination;

tests/fs_additional.rs

Lines changed: 324 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,28 @@
55
#[macro_use]
66
mod sys_common;
77

8-
use cap_std::fs::{DirBuilder, OpenOptions};
8+
use cap_std::fs::{Dir, DirBuilder, OpenOptions};
99
use cap_std::time::SystemClock;
1010
use std::io::{self, Read, Write};
1111
use std::path::Path;
1212
use std::str;
13-
use sys_common::io::{tmpdir, TempDir};
13+
use sys_common::io::tmpdir;
1414
use sys_common::symlink_supported;
1515

1616
#[cfg(not(windows))]
17-
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> {
17+
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> {
1818
tmpdir.symlink(src, dst)
1919
}
2020
#[cfg(not(windows))]
21-
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(
22-
src: P,
23-
tmpdir: &TempDir,
24-
dst: Q,
25-
) -> io::Result<()> {
21+
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> {
2622
tmpdir.symlink(src, dst)
2723
}
2824
#[cfg(windows)]
29-
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> {
25+
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> {
3026
tmpdir.symlink_dir(src, dst)
3127
}
3228
#[cfg(windows)]
33-
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(
34-
src: P,
35-
tmpdir: &TempDir,
36-
dst: Q,
37-
) -> io::Result<()> {
29+
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> {
3830
tmpdir.symlink_file(src, dst)
3931
}
4032

@@ -1046,3 +1038,321 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) {
10461038
assert_eq!(std.blocks(), cap_std::fs::MetadataExt::blocks(cap));
10471039
}
10481040
}
1041+
1042+
/// Test that a symlink in the middle of a path containing ".." doesn't cause
1043+
/// the path to be treated as if it ends with "..".
1044+
#[test]
1045+
fn dotdot_in_middle_of_symlink() {
1046+
let tmpdir = tmpdir();
1047+
1048+
let foo = b"foo";
1049+
check!(tmpdir.write("target", foo));
1050+
check!(tmpdir.create_dir("b"));
1051+
let b = check!(tmpdir.open_dir("b"));
1052+
check!(symlink_dir("..", &b, "up"));
1053+
1054+
let path = "b/up/target";
1055+
let mut file = check!(tmpdir.open(path));
1056+
let mut data = Vec::new();
1057+
check!(file.read_to_end(&mut data));
1058+
assert_eq!(data, foo);
1059+
}
1060+
1061+
/// Like `dotdot_in_middle_of_symlink` but with a `/.` at the end.
1062+
///
1063+
/// Windows doesn't appear to like symlinks that end with `/.`.
1064+
#[test]
1065+
#[cfg_attr(windows, ignore)]
1066+
fn dotdot_slashdot_in_middle_of_symlink() {
1067+
let tmpdir = tmpdir();
1068+
1069+
let foo = b"foo";
1070+
check!(tmpdir.write("target", foo));
1071+
check!(tmpdir.create_dir("b"));
1072+
let b = check!(tmpdir.open_dir("b"));
1073+
check!(symlink_dir("../.", &b, "up"));
1074+
1075+
let path = "b/up/target";
1076+
let mut file = check!(tmpdir.open(path));
1077+
let mut data = Vec::new();
1078+
check!(file.read_to_end(&mut data));
1079+
assert_eq!(data, foo);
1080+
}
1081+
1082+
/// Same as `dotdot_in_middle_of_symlink`, but use two levels of `..`.
1083+
///
1084+
/// Windows doesn't appear to like symlinks that end with `/..`.
1085+
#[test]
1086+
#[cfg_attr(windows, ignore)]
1087+
fn dotdot_more_in_middle_of_symlink() {
1088+
let tmpdir = tmpdir();
1089+
1090+
let foo = b"foo";
1091+
check!(tmpdir.write("target", foo));
1092+
check!(tmpdir.create_dir_all("b/c"));
1093+
let b = check!(tmpdir.open_dir("b"));
1094+
check!(symlink_dir("c/../..", &b, "up"));
1095+
1096+
let path = "b/up/target";
1097+
let mut file = check!(tmpdir.open(path));
1098+
let mut data = Vec::new();
1099+
check!(file.read_to_end(&mut data));
1100+
assert_eq!(data, foo);
1101+
}
1102+
1103+
/// Like `dotdot_more_in_middle_of_symlink`, but with a `/.` at the end.
1104+
///
1105+
/// Windows doesn't appear to like symlinks that end with `/.`.
1106+
#[test]
1107+
#[cfg_attr(windows, ignore)]
1108+
fn dotdot_slashdot_more_in_middle_of_symlink() {
1109+
let tmpdir = tmpdir();
1110+
1111+
let foo = b"foo";
1112+
check!(tmpdir.write("target", foo));
1113+
check!(tmpdir.create_dir_all("b/c"));
1114+
let b = check!(tmpdir.open_dir("b"));
1115+
check!(symlink_dir("c/../../.", &b, "up"));
1116+
1117+
let path = "b/up/target";
1118+
let mut file = check!(tmpdir.open(path));
1119+
let mut data = Vec::new();
1120+
check!(file.read_to_end(&mut data));
1121+
assert_eq!(data, foo);
1122+
}
1123+
1124+
/// Same as `dotdot_more_in_middle_of_symlink`, but the symlink doesn't
1125+
/// include `c`.
1126+
///
1127+
/// Windows doesn't appear to like symlinks that end with `/..`.
1128+
#[test]
1129+
#[cfg_attr(windows, ignore)]
1130+
fn dotdot_other_in_middle_of_symlink() {
1131+
let tmpdir = tmpdir();
1132+
1133+
let foo = b"foo";
1134+
check!(tmpdir.write("target", foo));
1135+
check!(tmpdir.create_dir_all("b/c"));
1136+
let c = check!(tmpdir.open_dir("b/c"));
1137+
check!(symlink_dir("../..", &c, "up"));
1138+
1139+
let path = "b/c/up/target";
1140+
let mut file = check!(tmpdir.open(path));
1141+
let mut data = Vec::new();
1142+
check!(file.read_to_end(&mut data));
1143+
assert_eq!(data, foo);
1144+
}
1145+
1146+
/// Like `dotdot_other_in_middle_of_symlink`, but with `/.` at the end.
1147+
///
1148+
/// Windows doesn't appear to like symlinks that end with `/.`.
1149+
#[test]
1150+
#[cfg_attr(windows, ignore)]
1151+
fn dotdot_slashdot_other_in_middle_of_symlink() {
1152+
let tmpdir = tmpdir();
1153+
1154+
let foo = b"foo";
1155+
check!(tmpdir.write("target", foo));
1156+
check!(tmpdir.create_dir_all("b/c"));
1157+
let c = check!(tmpdir.open_dir("b/c"));
1158+
check!(symlink_dir("../../.", &c, "up"));
1159+
1160+
let path = "b/c/up/target";
1161+
let mut file = check!(tmpdir.open(path));
1162+
let mut data = Vec::new();
1163+
check!(file.read_to_end(&mut data));
1164+
assert_eq!(data, foo);
1165+
}
1166+
1167+
/// Same as `dotdot_more_in_middle_of_symlink`, but use a symlink that
1168+
/// doesn't end with `..`.
1169+
#[test]
1170+
fn dotdot_even_more_in_middle_of_symlink() {
1171+
let tmpdir = tmpdir();
1172+
1173+
let foo = b"foo";
1174+
check!(tmpdir.create_dir_all("b/c"));
1175+
check!(tmpdir.write("b/target", foo));
1176+
let b = check!(tmpdir.open_dir("b"));
1177+
check!(symlink_dir("c/../../b", &b, "up"));
1178+
1179+
let path = "b/up/target";
1180+
let mut file = check!(tmpdir.open(path));
1181+
let mut data = Vec::new();
1182+
check!(file.read_to_end(&mut data));
1183+
assert_eq!(data, foo);
1184+
}
1185+
1186+
/// Like `dotdot_even_more_in_middle_of_symlink`, but with a `/.` at the end.
1187+
///
1188+
/// Windows doesn't appear to like symlinks that end with `/.`.
1189+
#[test]
1190+
#[cfg_attr(windows, ignore)]
1191+
fn dotdot_slashdot_even_more_in_middle_of_symlink() {
1192+
let tmpdir = tmpdir();
1193+
1194+
let foo = b"foo";
1195+
check!(tmpdir.create_dir_all("b/c"));
1196+
check!(tmpdir.write("b/target", foo));
1197+
let b = check!(tmpdir.open_dir("b"));
1198+
check!(symlink_dir("c/../../b/.", &b, "up"));
1199+
1200+
let path = "b/up/target";
1201+
let mut file = check!(tmpdir.open(path));
1202+
let mut data = Vec::new();
1203+
check!(file.read_to_end(&mut data));
1204+
assert_eq!(data, foo);
1205+
}
1206+
1207+
/// Same as `dotdot_even_more_in_middle_of_symlink`, but the symlink doesn't
1208+
/// include `c`.
1209+
#[test]
1210+
fn dotdot_even_other_in_middle_of_symlink() {
1211+
let tmpdir = tmpdir();
1212+
1213+
let foo = b"foo";
1214+
check!(tmpdir.create_dir_all("b/c"));
1215+
check!(tmpdir.write("b/target", foo));
1216+
let c = check!(tmpdir.open_dir("b/c"));
1217+
check!(symlink_dir("../../b", &c, "up"));
1218+
1219+
let path = "b/c/up/target";
1220+
let mut file = check!(tmpdir.open(path));
1221+
let mut data = Vec::new();
1222+
check!(file.read_to_end(&mut data));
1223+
assert_eq!(data, foo);
1224+
}
1225+
1226+
/// Like `dotdot_even_other_in_middle_of_symlink`, but with a `/.` at the end.
1227+
///
1228+
/// Windows doesn't appear to like symlinks that end with `/.`.
1229+
#[test]
1230+
#[cfg_attr(windows, ignore)]
1231+
fn dotdot_slashdot_even_other_in_middle_of_symlink() {
1232+
let tmpdir = tmpdir();
1233+
1234+
let foo = b"foo";
1235+
check!(tmpdir.create_dir_all("b/c"));
1236+
check!(tmpdir.write("b/target", foo));
1237+
let c = check!(tmpdir.open_dir("b/c"));
1238+
check!(symlink_dir("../../b/.", &c, "up"));
1239+
1240+
let path = "b/c/up/target";
1241+
let mut file = check!(tmpdir.open(path));
1242+
let mut data = Vec::new();
1243+
check!(file.read_to_end(&mut data));
1244+
assert_eq!(data, foo);
1245+
}
1246+
1247+
/// Similar to `dotdot_in_middle_of_symlink`, but this time the symlink to
1248+
/// `..` does happen to be the end of the path, so we need to make sure
1249+
/// the implementation doesn't just do a stack pop when it sees the `..`
1250+
/// leaving us with an `O_PATH` directory handle.
1251+
#[test]
1252+
fn dotdot_at_end_of_symlink() {
1253+
let tmpdir = tmpdir();
1254+
1255+
let foo = b"foo";
1256+
check!(tmpdir.write("target", foo));
1257+
check!(tmpdir.create_dir("b"));
1258+
let b = check!(tmpdir.open_dir("b"));
1259+
check!(symlink_dir("..", &b, "up"));
1260+
1261+
// Do some things with `path` that might break with an `O_PATH` fd.
1262+
// On Linux, the `permissions` part doesn't because cap-std uses
1263+
// /proc/self/fd. But the `read_dir` part does.
1264+
let path = "b/up";
1265+
1266+
let perms = check!(tmpdir.metadata(path)).permissions();
1267+
check!(tmpdir.set_permissions(path, perms));
1268+
1269+
let contents = check!(tmpdir.read_dir(path));
1270+
for entry in contents {
1271+
let _entry = check!(entry);
1272+
}
1273+
}
1274+
1275+
/// Like `dotdot_at_end_of_symlink`, but with a `/.` at the end.
1276+
///
1277+
/// Windows doesn't appear to like symlinks that end with `/.`.
1278+
#[test]
1279+
#[cfg_attr(windows, ignore)]
1280+
fn dotdot_slashdot_at_end_of_symlink() {
1281+
let tmpdir = tmpdir();
1282+
1283+
let foo = b"foo";
1284+
check!(tmpdir.write("target", foo));
1285+
check!(tmpdir.create_dir("b"));
1286+
let b = check!(tmpdir.open_dir("b"));
1287+
check!(symlink_dir("../.", &b, "up"));
1288+
1289+
// Do some things with `path` that might break with an `O_PATH` fd.
1290+
// On Linux, the `permissions` part doesn't because cap-std uses
1291+
// /proc/self/fd. But the `read_dir` part does.
1292+
let path = "b/up";
1293+
1294+
let perms = check!(tmpdir.metadata(path)).permissions();
1295+
check!(tmpdir.set_permissions(path, perms));
1296+
1297+
let contents = check!(tmpdir.read_dir(path));
1298+
for entry in contents {
1299+
let _entry = check!(entry);
1300+
}
1301+
}
1302+
1303+
/// Like `dotdot_at_end_of_symlink`, but do everything inside a new directory,
1304+
/// so that `MaybeOwnedFile` doesn't reopen `.` which would artificially give
1305+
/// us a non-`O_PATH` fd.
1306+
#[test]
1307+
fn dotdot_at_end_of_symlink_all_inside_dir() {
1308+
let tmpdir = tmpdir();
1309+
1310+
let foo = b"foo";
1311+
check!(tmpdir.create_dir("dir"));
1312+
check!(tmpdir.write("dir/target", foo));
1313+
check!(tmpdir.create_dir("dir/b"));
1314+
let b = check!(tmpdir.open_dir("dir/b"));
1315+
check!(symlink_dir("..", &b, "up"));
1316+
1317+
// Do some things with `path` that might break with an `O_PATH` fd.
1318+
// On Linux, the `permissions` part doesn't because cap-std uses
1319+
// /proc/self/fd. But the `read_dir` part does.
1320+
let path = "dir/b/up";
1321+
1322+
let perms = check!(tmpdir.metadata(path)).permissions();
1323+
check!(tmpdir.set_permissions(path, perms));
1324+
1325+
let contents = check!(tmpdir.read_dir(path));
1326+
for entry in contents {
1327+
let _entry = check!(entry);
1328+
}
1329+
}
1330+
1331+
/// `dotdot_at_end_of_symlink_all_inside_dir`, but with a `/.` at the end.
1332+
///
1333+
/// Windows doesn't appear to like symlinks that end with `/.`.
1334+
#[test]
1335+
#[cfg_attr(windows, ignore)]
1336+
fn dotdot_slashdot_at_end_of_symlink_all_inside_dir() {
1337+
let tmpdir = tmpdir();
1338+
1339+
let foo = b"foo";
1340+
check!(tmpdir.create_dir("dir"));
1341+
check!(tmpdir.write("dir/target", foo));
1342+
check!(tmpdir.create_dir("dir/b"));
1343+
let b = check!(tmpdir.open_dir("dir/b"));
1344+
check!(symlink_dir("../.", &b, "up"));
1345+
1346+
// Do some things with `path` that might break with an `O_PATH` fd.
1347+
// On Linux, the `permissions` part doesn't because cap-std uses
1348+
// /proc/self/fd. But the `read_dir` part does.
1349+
let path = "dir/b/up";
1350+
1351+
let perms = check!(tmpdir.metadata(path)).permissions();
1352+
check!(tmpdir.set_permissions(path, perms));
1353+
1354+
let contents = check!(tmpdir.read_dir(path));
1355+
for entry in contents {
1356+
let _entry = check!(entry);
1357+
}
1358+
}

0 commit comments

Comments
 (0)