Scripts: a missing file below a link that leads nowhere is refused

A missing file below a folder link whose target does not exist got
"does not exist", while one below a link to an existing outside folder
was refused, so a script could tell whether the folder a link leads to
exists. A missing path is now refused when the nearest entry above it
that exists is a link that leads to nothing.
This commit is contained in:
dotCooCoo
2026-11-11 15:53:26 -08:00
parent 7114de8ae5
commit 335acab01b
3 changed files with 19 additions and 3 deletions
+7 -2
View File
@@ -478,9 +478,14 @@ impl ScriptScope {
/// Is the missing `path` inside a granted folder: does the nearest folder above it that exists
/// lie inside one? On a file system where case matters, `MAIN/x` beside the granted `main` is
/// outside, whether `MAIN` exists or not.
/// outside, whether `MAIN` exists or not. A link above it that leads to nothing is outside too,
/// so a script cannot tell whether the folder a link leads to exists.
fn missing_inside(&self, path: &Path) -> bool {
path.ancestors().skip(1).find_map(|a| std::fs::canonicalize(a).ok()).is_some_and(|real| self.roots.iter().any(|r| inside(&real, r)))
path.ancestors()
.skip(1)
.find(|a| std::fs::symlink_metadata(a).is_ok())
.and_then(|a| std::fs::canonicalize(a).ok())
.is_some_and(|real| self.roots.iter().any(|r| inside(&real, r)))
}
/// What the file or folder at `path` is, located as [`ScriptScope::locate`] locates it.
+11
View File
@@ -156,6 +156,17 @@ fn script_file_link_escape_refused() {
// A missing file behind the link is refused like an existing one, so nothing tells them apart.
let log = logged(&base, "var n = new File('away/nope.txt'); vc.log([n.exists, n.open('r'), n.error]);");
assert_eq!(log, json!([[false, false, DENIED]]));
// A folder link to an outside folder that does not exist is refused the same way, so a script
// cannot tell whether the folder it leads to exists.
link_folder(&base.join("gone"), &base.join("main").join("dangling"));
let log = logged(
&base,
"var d = new File('dangling/nope.txt'); var e = new File('dangling/a/b.txt');\n\
vc.log([d.exists, d.open('r'), d.error, e.open('r'), e.error]);",
);
assert_eq!(log, json!([[false, false, DENIED, false, DENIED]]));
let dangling = base.join("main").join("dangling");
std::fs::remove_dir(&dangling).or_else(|_| std::fs::remove_file(&dangling)).unwrap();
#[cfg(unix)]
let made = |target: &Path, link: &Path| std::os::unix::fs::symlink(target, link).is_ok();
#[cfg(windows)]
+1 -1
View File
@@ -1838,7 +1838,7 @@ VectorCraft reads files by these rules of its own:
(`\\?\`, `\\.\`) are refused before anything is looked up: `exists` is `false`, the dates and `getFiles` give `null`,
and `error` is `Script file access denied`. A path that leads out through a link or a junction is refused too, and
`getFiles` leaves such an entry out. A missing file is refused the same way unless the nearest folder above it that
exists is inside the script's folder.
exists is inside the script's folder. It is also refused when a link above it leads to a folder that does not exist.
- `open` reads the whole file. A file larger than 8 MiB is refused from its size before any of it is read, with
`Script file exceeds 8 MiB`, and a run that opens more than 32 MiB in all ends with an error. A run has at most 256
files open at once; another `open` returns `false` with `Script has 256 files open` until one is closed.