use crate::{anchored_canonicalize, soft_canonicalize};
use std::fs;
use tempfile::TempDir;
#[test]
fn anchored_relative_symlink_keeps_clamp_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
let td = TempDir::new()?;
let anchor = td.path().join("home").join("jail");
let target_parent = td.path().join("opt").join("subdir");
fs::create_dir_all(&anchor)?;
fs::create_dir_all(&target_parent)?;
let special_target = target_parent.join("special");
fs::create_dir(special_target)?;
let clamped_base = anchor.join("opt").join("subdir");
fs::create_dir_all(&clamped_base)?;
let hello_world = clamped_base.join("hello").join("world");
fs::create_dir_all(hello_world)?;
let base = soft_canonicalize(&anchor)?;
let link = base.join("special");
let relative_target = r"..\..\opt\subdir\special";
match symlink_dir(relative_target, link) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let out = anchored_canonicalize(&base, r"special\..\hello\world")?;
let expected = base.join("opt").join("subdir").join("hello").join("world");
assert_eq!(out, expected);
Ok(())
}
#[test]
fn anchored_symlink_or_junction_keeps_clamp_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
let td = TempDir::new()?;
let anchor = td.path().join("home").join("jail");
fs::create_dir_all(&anchor)?;
fs::create_dir_all(anchor.join("opt").join("subdir").join("special"))?;
fs::create_dir_all(
anchor
.join("opt")
.join("subdir")
.join("hello")
.join("world"),
)?;
let base = soft_canonicalize(&anchor)?;
let link = base.join("special");
let absolute_target = base.join("opt").join("subdir").join("special");
match symlink_dir(&absolute_target, &link) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) => {
junction_verbatim::create(&absolute_target, &link)?;
}
Err(e) => return Err(e),
}
let out = anchored_canonicalize(&base, r"special\..\hello\world")?;
let expected = base.join("opt").join("subdir").join("hello").join("world");
assert_eq!(out, expected);
Ok(())
}
#[test]
fn anchored_absolute_symlink_is_clamped_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
let td = TempDir::new()?;
let anchor = td.path().join("a").join("b");
let outside = td.path().join("outside").join("place");
fs::create_dir_all(&anchor)?;
fs::create_dir_all(&outside)?;
let base = soft_canonicalize(&anchor)?;
let abs_outside = soft_canonicalize(&outside)?;
let link = base.join("escape");
match symlink_dir(abs_outside, link) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let out = anchored_canonicalize(&base, r"escape")?;
assert!(
out.starts_with(&base),
"Absolute symlink should be clamped to anchor. Got: {:?}, Anchor: {:?}",
out,
base
);
Ok(())
}
#[test]
fn non_existing_anchor_with_input_absolute_symlink_is_clamped_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
let td = TempDir::new()?;
let root = td.path().join("root");
let target = td.path().join("target");
let outside = td.path().join("outside");
fs::create_dir_all(&root)?;
fs::create_dir_all(&target)?;
fs::create_dir_all(&outside)?;
match symlink_dir(&target, root.join("linked")) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
fs::create_dir_all(target.join("miss"))?;
let abs_outside = soft_canonicalize(&outside)?;
match symlink_dir(abs_outside, target.join("miss").join("escape")) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let anchor = root.join("linked").join("miss");
let out = anchored_canonicalize(&anchor, r"escape")?;
let canon_anchor = soft_canonicalize(&anchor)?;
assert!(
out.starts_with(&canon_anchor),
"Absolute symlink should be clamped to anchor. Got: {:?}, Anchor: {:?}",
out,
canon_anchor
);
Ok(())
}
#[test]
fn anchored_toctou_symlink_swap_to_unc_is_clamped_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
use std::thread;
use std::time::Duration;
let td = TempDir::new()?;
let anchor = td.path().join("root");
let safe_target = td.path().join("safe");
std::fs::create_dir_all(&anchor)?;
std::fs::create_dir_all(&safe_target)?;
let base = soft_canonicalize(&anchor)?;
let link = base.join("esc");
match symlink_dir(&safe_target, &link) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let link_swap = link;
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
let _ = std::fs::remove_dir(&link_swap);
let _ = symlink_dir(r"\\?\UNC\server\share", &link_swap);
});
let out = anchored_canonicalize(&base, r"esc\child.txt")?;
handle.join().ok();
assert!(out.starts_with(&base));
Ok(())
}
#[test]
fn anchored_toctou_symlink_swap_to_device_is_clamped_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
use std::thread;
use std::time::Duration;
let td = TempDir::new()?;
let anchor = td.path().join("root");
let safe_target = td.path().join("safe");
std::fs::create_dir_all(&anchor)?;
std::fs::create_dir_all(&safe_target)?;
let base = soft_canonicalize(&anchor)?;
let link_dir = base.join("dirlink");
match symlink_dir(&safe_target, &link_dir) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let link_swap = link_dir;
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
let _ = std::fs::remove_dir(&link_swap);
let _ = symlink_dir(r"\\.\PIPE", &link_swap);
});
let out = anchored_canonicalize(&base, r"dirlink\child.txt")?;
handle.join().ok();
assert!(out.starts_with(&base));
Ok(())
}
#[test]
fn anchored_absolute_symlink_to_device_is_clamped_windows() -> std::io::Result<()> {
use std::io;
use std::os::windows::fs::symlink_dir;
let td = TempDir::new()?;
let anchor = td.path().join("base");
std::fs::create_dir_all(&anchor)?;
let abs_target = r"C:\\Windows\\System32";
let base = soft_canonicalize(&anchor)?;
let link = base.join("escape");
match symlink_dir(abs_target, link) {
Ok(_) => {}
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied || e.raw_os_error() == Some(1314) {
eprintln!("skipping: symlink creation not permitted on this Windows environment");
return Ok(());
}
return Err(e);
}
}
let out = anchored_canonicalize(&base, "escape")?;
let expected = base.join(r"Windows\System32");
assert_eq!(out, expected);
Ok(())
}