Skip to content

Commit 992e998

Browse files
authored
fix(fs): improve file utime checks (#30872)
Improves the checks on FsFile.prototype.utime
1 parent 24295d1 commit 992e998

File tree

7 files changed

+103
-12
lines changed

7 files changed

+103
-12
lines changed

cli/rt/file_system.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,12 @@ impl FileBackedVfsFile {
12541254

12551255
#[async_trait::async_trait(?Send)]
12561256
impl deno_io::fs::File for FileBackedVfsFile {
1257+
fn maybe_path(&self) -> Option<&Path> {
1258+
// ok because a vfs file will never be written to and this
1259+
// method is only used for checking write permissions
1260+
None
1261+
}
1262+
12571263
fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize> {
12581264
self.read_to_buf(buf).map_err(Into::into)
12591265
}

ext/fs/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ deno_core::extension!(deno_fs,
178178
op_fs_funlock_sync,
179179
op_fs_ftruncate_sync,
180180
op_fs_file_truncate_async,
181-
op_fs_futime_sync,
182-
op_fs_futime_async,
181+
op_fs_futime_sync<P>,
182+
op_fs_futime_async<P>,
183183

184184
],
185185
esm = [ "30_fs.js" ],

ext/fs/ops.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,7 +1868,7 @@ pub async fn op_fs_file_truncate_async(
18681868
}
18691869

18701870
#[op2(fast)]
1871-
pub fn op_fs_futime_sync(
1871+
pub fn op_fs_futime_sync<P: FsPermissions + 'static>(
18721872
state: &mut OpState,
18731873
#[smi] rid: ResourceId,
18741874
#[number] atime_secs: i64,
@@ -1878,12 +1878,19 @@ pub fn op_fs_futime_sync(
18781878
) -> Result<(), FsOpsError> {
18791879
let file =
18801880
FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?;
1881+
if let Some(path) = file.maybe_path() {
1882+
_ = state.borrow::<P>().check_open(
1883+
Cow::Borrowed(path),
1884+
OpenAccessKind::WriteNoFollow,
1885+
"Deno.FsFile.prototype.utimeSync()",
1886+
)?;
1887+
}
18811888
file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?;
18821889
Ok(())
18831890
}
18841891

18851892
#[op2(async)]
1886-
pub async fn op_fs_futime_async(
1893+
pub async fn op_fs_futime_async<P: FsPermissions + 'static>(
18871894
state: Rc<RefCell<OpState>>,
18881895
#[smi] rid: ResourceId,
18891896
#[number] atime_secs: i64,
@@ -1893,6 +1900,13 @@ pub async fn op_fs_futime_async(
18931900
) -> Result<(), FsOpsError> {
18941901
let file = FileResource::get_file(&state.borrow(), rid)
18951902
.map_err(FsOpsErrorKind::Resource)?;
1903+
if let Some(path) = file.maybe_path() {
1904+
_ = state.borrow().borrow::<P>().check_open(
1905+
Cow::Borrowed(path),
1906+
OpenAccessKind::WriteNoFollow,
1907+
"Deno.FsFile.prototype.utime()",
1908+
)?;
1909+
}
18961910
file
18971911
.utime_async(atime_secs, atime_nanos, mtime_secs, mtime_nanos)
18981912
.await?;

ext/fs/std_fs.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,21 @@ impl FileSystem for RealFs {
8585
options: OpenOptions,
8686
) -> FsResult<Rc<dyn File>> {
8787
let std_file = open_with_checked_path(options, path)?;
88-
Ok(Rc::new(StdFileResourceInner::file(std_file)))
88+
Ok(Rc::new(StdFileResourceInner::file(
89+
std_file,
90+
Some(path.to_path_buf()),
91+
)))
8992
}
9093
async fn open_async<'a>(
9194
&'a self,
9295
path: CheckedPathBuf,
9396
options: OpenOptions,
9497
) -> FsResult<Rc<dyn File>> {
9598
let std_file = open_with_checked_path(options, &path.as_checked_path())?;
96-
Ok(Rc::new(StdFileResourceInner::file(std_file)))
99+
Ok(Rc::new(StdFileResourceInner::file(
100+
std_file,
101+
Some(path.to_path_buf()),
102+
)))
97103
}
98104

99105
fn mkdir_sync(

ext/io/fs.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::borrow::Cow;
44
use std::fmt::Formatter;
55
use std::io;
6+
use std::path::Path;
67
#[cfg(unix)]
78
use std::process::Stdio as StdStdio;
89
use std::rc::Rc;
@@ -207,6 +208,10 @@ impl FsStat {
207208

208209
#[async_trait::async_trait(?Send)]
209210
pub trait File {
211+
/// Provides the path of the file, which is used for checking
212+
/// metadata permission updates.
213+
fn maybe_path(&self) -> Option<&Path>;
214+
210215
fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize>;
211216
async fn read(self: Rc<Self>, limit: usize) -> FsResult<BufView> {
212217
let buf = BufMutView::new(limit);

ext/io/lib.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use std::os::fd::AsRawFd;
1515
use std::os::unix::io::FromRawFd;
1616
#[cfg(windows)]
1717
use std::os::windows::io::FromRawHandle;
18+
use std::path::Path;
19+
use std::path::PathBuf;
1820
#[cfg(unix)]
1921
use std::process::Stdio as StdStdio;
2022
use std::rc::Rc;
@@ -255,8 +257,9 @@ deno_core::extension!(deno_io,
255257
StdioPipeInner::Inherit => StdFileResourceInner::new(
256258
StdFileResourceKind::Stdin(stdin_state),
257259
STDIN_HANDLE.try_clone().unwrap(),
260+
None,
258261
),
259-
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe),
262+
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
260263
}),
261264
"stdin".to_string(),
262265
));
@@ -267,8 +270,9 @@ deno_core::extension!(deno_io,
267270
StdioPipeInner::Inherit => StdFileResourceInner::new(
268271
StdFileResourceKind::Stdout,
269272
STDOUT_HANDLE.try_clone().unwrap(),
273+
None,
270274
),
271-
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe),
275+
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
272276
}),
273277
"stdout".to_string(),
274278
));
@@ -279,8 +283,9 @@ deno_core::extension!(deno_io,
279283
StdioPipeInner::Inherit => StdFileResourceInner::new(
280284
StdFileResourceKind::Stderr,
281285
STDERR_HANDLE.try_clone().unwrap(),
286+
None,
282287
),
283-
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe),
288+
StdioPipeInner::File(pipe) => StdFileResourceInner::file(pipe, None),
284289
}),
285290
"stderr".to_string(),
286291
));
@@ -494,21 +499,27 @@ pub struct StdFileResourceInner {
494499
// to occur at a time
495500
cell_async_task_queue: Rc<TaskQueue>,
496501
handle: ResourceHandleFd,
502+
maybe_path: Option<PathBuf>,
497503
}
498504

499505
impl StdFileResourceInner {
500-
pub fn file(fs_file: StdFile) -> Self {
501-
StdFileResourceInner::new(StdFileResourceKind::File, fs_file)
506+
pub fn file(fs_file: StdFile, maybe_path: Option<PathBuf>) -> Self {
507+
StdFileResourceInner::new(StdFileResourceKind::File, fs_file, maybe_path)
502508
}
503509

504-
fn new(kind: StdFileResourceKind, fs_file: StdFile) -> Self {
510+
fn new(
511+
kind: StdFileResourceKind,
512+
fs_file: StdFile,
513+
maybe_path: Option<PathBuf>,
514+
) -> Self {
505515
// We know this will be an fd
506516
let handle = ResourceHandle::from_fd_like(&fs_file).as_fd_like().unwrap();
507517
StdFileResourceInner {
508518
kind,
509519
handle,
510520
cell: RefCell::new(Some(fs_file)),
511521
cell_async_task_queue: Default::default(),
522+
maybe_path,
512523
}
513524
}
514525

@@ -659,6 +670,10 @@ impl StdFileResourceInner {
659670

660671
#[async_trait::async_trait(?Send)]
661672
impl crate::fs::File for StdFileResourceInner {
673+
fn maybe_path(&self) -> Option<&Path> {
674+
self.maybe_path.as_deref()
675+
}
676+
662677
fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize> {
663678
// Rust will line buffer and we don't want that behavior
664679
// (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr.
@@ -1101,6 +1116,7 @@ impl crate::fs::File for StdFileResourceInner {
11011116
cell: RefCell::new(Some(inner.try_clone()?)),
11021117
cell_async_task_queue: Default::default(),
11031118
handle: self.handle,
1119+
maybe_path: self.maybe_path.clone(),
11041120
})),
11051121
None => Err(FsError::FileBusy),
11061122
}

tests/unit/utime_test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@ Deno.test(
2828
},
2929
);
3030

31+
Deno.test(
32+
{ permissions: { read: true, write: true } },
33+
async function fsFileUtimeFailPermissions() {
34+
const testDir = Deno.makeTempDirSync();
35+
const filename = testDir + "/file.txt";
36+
Deno.writeTextFileSync(filename, "");
37+
Deno.permissions.revokeSync({ name: "write" });
38+
using file = await Deno.open(filename, {
39+
read: true,
40+
write: false,
41+
});
42+
43+
const atime = 1000;
44+
const mtime = 50000;
45+
await assertRejects(
46+
() => file.utime(atime, mtime),
47+
Deno.errors.NotCapable,
48+
"Requires write access to",
49+
);
50+
},
51+
);
52+
3153
Deno.test(
3254
{ permissions: { read: true, write: true } },
3355
function futimeSyncSuccess() {
@@ -49,6 +71,28 @@ Deno.test(
4971
},
5072
);
5173

74+
Deno.test(
75+
{ permissions: { read: true, write: true } },
76+
function fsFileUtimeSyncFailPermissions() {
77+
const testDir = Deno.makeTempDirSync();
78+
const filename = testDir + "/file.txt";
79+
Deno.writeTextFileSync(filename, "");
80+
Deno.permissions.revokeSync({ name: "write" });
81+
using file = Deno.openSync(filename, {
82+
read: true,
83+
write: false,
84+
});
85+
86+
const atime = 1000;
87+
const mtime = 50000;
88+
assertThrows(
89+
() => file.utimeSync(atime, mtime),
90+
Deno.errors.NotCapable,
91+
"Requires write access to",
92+
);
93+
},
94+
);
95+
5296
Deno.test(
5397
{ permissions: { read: true, write: true } },
5498
function utimeSyncFileSuccess() {

0 commit comments

Comments
 (0)