diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 70d1abae7c..06db9958c8 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -1680,7 +1680,46 @@ impl PermissionsContainer { return Ok(()); } + /// We'll allow opening /proc/self/fd/{n} without additional permissions under the following conditions: + /// + /// 1. n > 2. This allows for opening bash-style redirections, but not stdio + /// 2. the fd referred to by n is a pipe + #[cfg(unix)] + fn is_fd_file_is_pipe(path: &Path) -> bool { + if let Some(fd) = path.file_name() { + if let Ok(s) = std::str::from_utf8(fd.as_encoded_bytes()) { + if let Ok(n) = s.parse::() { + if n > 2 { + // SAFETY: This is proper use of the stat syscall + unsafe { + let mut stat = std::mem::zeroed::(); + if libc::fstat(n, &mut stat as _) == 0 + && ((stat.st_mode & libc::S_IFMT) & libc::S_IFIFO) != 0 + { + return true; + } + }; + } + } + } + } + false + } + + // On unixy systems, we allow opening /dev/fd/XXX for valid FDs that + // are pipes. + #[cfg(unix)] + if path.starts_with("/dev/fd") && is_fd_file_is_pipe(path) { + return Ok(()); + } + if cfg!(target_os = "linux") { + // On Linux, we also allow opening /proc/self/fd/XXX for valid FDs that + // are pipes. + #[cfg(unix)] + if path.starts_with("/proc/self/fd") && is_fd_file_is_pipe(path) { + return Ok(()); + } if path.starts_with("/dev") || path.starts_with("/proc") || path.starts_with("/sys") diff --git a/tests/specs/permission/proc_self_fd/__test__.jsonc b/tests/specs/permission/proc_self_fd/__test__.jsonc new file mode 100644 index 0000000000..8d4d1ed43d --- /dev/null +++ b/tests/specs/permission/proc_self_fd/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "args": "run -A main.js", + "output": "hi\n\n0\n", + "exitCode": 123 +} diff --git a/tests/specs/permission/proc_self_fd/main.js b/tests/specs/permission/proc_self_fd/main.js new file mode 100644 index 0000000000..86d8334cbb --- /dev/null +++ b/tests/specs/permission/proc_self_fd/main.js @@ -0,0 +1,18 @@ +// This test is Linux/Darwin only +if (Deno.build.os !== "linux" && Deno.build.os !== "darwin") { + console.log("hi\n\n0"); + Deno.exit(123); +} + +const cmd = new Deno.Command("/usr/bin/env", { + args: [ + "bash", + "-c", + [Deno.execPath(), "run", "--allow-read", "reader.ts", '<(echo "hi")'].join( + " ", + ), + ], +}).spawn(); + +console.log((await cmd.status).code); +Deno.exit(123); diff --git a/tests/specs/permission/proc_self_fd/reader.ts b/tests/specs/permission/proc_self_fd/reader.ts new file mode 100644 index 0000000000..4b3587fa2f --- /dev/null +++ b/tests/specs/permission/proc_self_fd/reader.ts @@ -0,0 +1 @@ +console.log(Deno.readTextFileSync(Deno.args[0]));