diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-05-11 13:13:27 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-11 13:13:27 +0200 |
commit | 32aeec9630dc91162f0408b95dd86e1c26e4c1d3 (patch) | |
tree | f93d8e6b665df0c3054dba56973712412916493e /cli/file_fetcher.rs | |
parent | 0d148c6e80583dfe029d5362f61b92334a22341a (diff) |
refactor: check permissions in SourceFileFetcher (#5011)
This PR hot-fixes permission escapes in dynamic imports, workers
and runtime compiler APIs.
"permissions" parameter was added to public APIs of SourceFileFetcher
and appropriate permission checks are performed during loading of
local and remote files.
Diffstat (limited to 'cli/file_fetcher.rs')
-rw-r--r-- | cli/file_fetcher.rs | 278 |
1 files changed, 238 insertions, 40 deletions
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index c4e0e4fec..2e75517e6 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -6,6 +6,7 @@ use crate::http_util::create_http_client; use crate::http_util::FetchOnceResult; use crate::msg; use crate::op_error::OpError; +use crate::permissions::Permissions; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use futures::future::FutureExt; @@ -109,6 +110,7 @@ impl SourceFileFetcher { pub fn fetch_cached_source_file( &self, specifier: &ModuleSpecifier, + permissions: Permissions, ) -> Option<SourceFile> { let maybe_source_file = self.source_file_cache.get(specifier.to_string()); @@ -123,7 +125,7 @@ impl SourceFileFetcher { // future, because it doesn't actually do any asynchronous // action in that path. if let Ok(maybe_source_file) = - self.get_source_file_from_local_cache(specifier.as_url()) + self.get_source_file_from_local_cache(specifier.as_url(), &permissions) { return maybe_source_file; } @@ -148,6 +150,7 @@ impl SourceFileFetcher { &self, specifier: &ModuleSpecifier, maybe_referrer: Option<ModuleSpecifier>, + permissions: Permissions, ) -> Result<SourceFile, ErrBox> { let module_url = specifier.as_url().to_owned(); debug!("fetch_source_file specifier: {} ", &module_url); @@ -167,6 +170,7 @@ impl SourceFileFetcher { self.use_disk_cache, self.no_remote, self.cached_only, + &permissions, ) .await; @@ -222,6 +226,7 @@ impl SourceFileFetcher { fn get_source_file_from_local_cache( &self, module_url: &Url, + permissions: &Permissions, ) -> Result<Option<SourceFile>, ErrBox> { let url_scheme = module_url.scheme(); let is_local_file = url_scheme == "file"; @@ -229,7 +234,7 @@ impl SourceFileFetcher { // Local files are always fetched from disk bypassing cache entirely. if is_local_file { - return self.fetch_local_file(&module_url).map(Some); + return self.fetch_local_file(&module_url, permissions).map(Some); } self.fetch_cached_remote_source(&module_url) @@ -252,6 +257,7 @@ impl SourceFileFetcher { use_disk_cache: bool, no_remote: bool, cached_only: bool, + permissions: &Permissions, ) -> Result<SourceFile, ErrBox> { let url_scheme = module_url.scheme(); let is_local_file = url_scheme == "file"; @@ -259,7 +265,7 @@ impl SourceFileFetcher { // Local files are always fetched from disk bypassing cache entirely. if is_local_file { - return self.fetch_local_file(&module_url); + return self.fetch_local_file(&module_url, permissions); } // The file is remote, fail if `no_remote` is true. @@ -276,18 +282,29 @@ impl SourceFileFetcher { // Fetch remote file and cache on-disk for subsequent access self - .fetch_remote_source(&module_url, use_disk_cache, cached_only, 10) + .fetch_remote_source( + &module_url, + use_disk_cache, + cached_only, + 10, + permissions, + ) .await } /// Fetch local source file. - fn fetch_local_file(&self, module_url: &Url) -> Result<SourceFile, ErrBox> { + fn fetch_local_file( + &self, + module_url: &Url, + permissions: &Permissions, + ) -> Result<SourceFile, ErrBox> { let filepath = module_url.to_file_path().map_err(|()| { ErrBox::from(OpError::uri_error( "File URL contains invalid path".to_owned(), )) })?; + permissions.check_read(&filepath)?; let source_code = match fs::read(filepath.clone()) { Ok(c) => c, Err(e) => return Err(e.into()), @@ -390,12 +407,17 @@ impl SourceFileFetcher { use_disk_cache: bool, cached_only: bool, redirect_limit: i64, + permissions: &Permissions, ) -> Pin<Box<dyn Future<Output = Result<SourceFile, ErrBox>>>> { if redirect_limit < 0 { let e = OpError::http("too many redirects".to_string()); return futures::future::err(e.into()).boxed_local(); } + if let Err(e) = permissions.check_net_url(&module_url) { + return futures::future::err(e.into()).boxed_local(); + } + let is_blacklisted = check_cache_blacklist(module_url, self.cache_blacklist.as_ref()); // First try local cache @@ -441,6 +463,7 @@ impl SourceFileFetcher { Ok((_, headers)) => headers.get("etag").map(String::from), Err(_) => None, }; + let permissions = permissions.clone(); let http_client = self.http_client.clone(); // Single pass fetch, either yields code or yields redirect. let f = async move { @@ -463,6 +486,7 @@ impl SourceFileFetcher { use_disk_cache, cached_only, redirect_limit - 1, + &permissions, ) .await } @@ -752,11 +776,15 @@ mod tests { if cfg!(windows) { // Should fail: missing drive letter. let u = Url::parse("file:///etc/passwd").unwrap(); - fetcher.fetch_local_file(&u).unwrap_err(); + fetcher + .fetch_local_file(&u, &Permissions::allow_all()) + .unwrap_err(); } else { // Should fail: local network paths are not supported on unix. let u = Url::parse("file://server/etc/passwd").unwrap(); - fetcher.fetch_local_file(&u).unwrap_err(); + fetcher + .fetch_local_file(&u, &Permissions::allow_all()) + .unwrap_err(); } } @@ -774,7 +802,13 @@ mod tests { let cache_filename = fetcher.http_cache.get_cache_filename(&module_url); let result = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -795,7 +829,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result2 = fetcher_1 - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result2.is_ok()); let r2 = result2.unwrap(); @@ -818,7 +858,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result3 = fetcher_2 - .get_source_file(&module_url_1, true, false, false) + .get_source_file( + &module_url_1, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result3.is_ok()); let r3 = result3.unwrap(); @@ -839,7 +885,13 @@ mod tests { // and don't use cache let fetcher = setup_file_fetcher(temp_dir.path()); let result4 = fetcher - .get_source_file(&module_url_2, false, false, false) + .get_source_file( + &module_url_2, + false, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result4.is_ok()); let r4 = result4.unwrap(); @@ -863,7 +915,13 @@ mod tests { let cache_filename = fetcher.http_cache.get_cache_filename(&module_url); let result = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -883,7 +941,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result2 = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result2.is_ok()); let r2 = result2.unwrap(); @@ -903,7 +967,13 @@ mod tests { // process) and don't use cache let fetcher = setup_file_fetcher(temp_dir.path()); let result3 = fetcher - .get_source_file(&module_url_1, false, false, false) + .get_source_file( + &module_url_1, + false, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result3.is_ok()); let r3 = result3.unwrap(); @@ -930,7 +1000,9 @@ mod tests { fetcher.http_cache.get_cache_filename(&specifier.as_url()); // first download - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); let headers_file_name = @@ -946,7 +1018,9 @@ mod tests { // `use_disk_cache` is set to false, this can be verified using source // header file creation timestamp (should be the same as after first // download) - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); let result = fs::File::open(&headers_file_name); @@ -984,7 +1058,13 @@ mod tests { // Test basic follow and headers recording let result = fetcher - .get_source_file(&redirect_module_url, true, false, false) + .get_source_file( + &redirect_module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1033,7 +1113,13 @@ mod tests { // Test double redirects and headers recording let result = fetcher - .get_source_file(&double_redirect_url, true, false, false) + .get_source_file( + &double_redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1080,7 +1166,13 @@ mod tests { // Test that redirect target is not downloaded twice for different redirect source. let result = fetcher - .get_source_file(&double_redirect_url, true, false, false) + .get_source_file( + &double_redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fs::File::open(&target_path); @@ -1095,7 +1187,13 @@ mod tests { // using source header file creation timestamp (should be the same as // after first `get_source_file`) let result = fetcher - .get_source_file(&redirect_url, true, false, false) + .get_source_file( + &redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fs::File::open(&target_path_); @@ -1121,12 +1219,24 @@ mod tests { // Test that redirections can be limited let result = fetcher - .fetch_remote_source(&double_redirect_url, false, false, 2) + .fetch_remote_source( + &double_redirect_url, + false, + false, + 2, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fetcher - .fetch_remote_source(&double_redirect_url, false, false, 1) + .fetch_remote_source( + &double_redirect_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1161,7 +1271,13 @@ mod tests { // Test basic follow and headers recording let result = fetcher - .get_source_file(&redirect_module_url, true, false, false) + .get_source_file( + &redirect_module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1193,7 +1309,13 @@ mod tests { Url::parse("http://localhost:4545/cli/tests/002_hello.ts").unwrap(); // Remote modules are not allowed let result = fetcher - .get_source_file(&module_url, true, true, false) + .get_source_file( + &module_url, + true, + true, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1216,7 +1338,13 @@ mod tests { // file hasn't been cached before let result = fetcher - .get_source_file(&module_url, true, false, true) + .get_source_file( + &module_url, + true, + false, + true, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1225,12 +1353,24 @@ mod tests { // download and cache file let result = fetcher_1 - .get_source_file(&module_url_1, true, false, false) + .get_source_file( + &module_url_1, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); // module is already cached, should be ok even with `cached_only` let result = fetcher_2 - .get_source_file(&module_url_2, true, false, true) + .get_source_file( + &module_url_2, + true, + false, + true, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); drop(http_server_guard); @@ -1244,7 +1384,13 @@ mod tests { Url::parse("http://127.0.0.1:4545/cli/tests/subdir/mt_video_mp2t.t3.ts") .unwrap(); let result = fetcher - .fetch_remote_source(&module_url, false, false, 10) + .fetch_remote_source( + &module_url, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -1289,7 +1435,13 @@ mod tests { let module_url_3_ = module_url_3.clone(); let result = fetcher - .fetch_remote_source(&module_url, false, false, 10) + .fetch_remote_source( + &module_url, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -1298,7 +1450,13 @@ mod tests { let (_, headers) = fetcher.http_cache.get(&module_url).unwrap(); assert_eq!(headers.get("content-type").unwrap(), "text/typescript"); let result = fetcher_1 - .fetch_remote_source(&module_url_2, false, false, 10) + .fetch_remote_source( + &module_url_2, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r2 = result.unwrap(); @@ -1309,7 +1467,13 @@ mod tests { // test unknown extension let result = fetcher_2 - .fetch_remote_source(&module_url_3, false, false, 10) + .fetch_remote_source( + &module_url_3, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r3 = result.unwrap(); @@ -1328,14 +1492,18 @@ mod tests { // Test failure case. let specifier = ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_err()); let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("js/main.ts"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1347,14 +1515,18 @@ mod tests { // Test failure case. let specifier = ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_err()); let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("js/main.ts"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1367,7 +1539,9 @@ mod tests { .join("tests/001_hello.js"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1611,7 +1785,13 @@ mod tests { Url::parse("http://127.0.0.1:4545/etag_script.ts").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); @@ -1633,7 +1813,13 @@ mod tests { let file_name = fetcher.http_cache.get_cache_filename(&module_url); let _ = fs::write(&file_name, "changed content"); let cached_source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await .unwrap(); assert_eq!(cached_source.source_code, b"changed content"); @@ -1732,7 +1918,13 @@ mod tests { let module_url = Url::parse("http://127.0.0.1:4545/xTypeScriptTypes.js").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); @@ -1752,7 +1944,13 @@ mod tests { let module_url = Url::parse("http://127.0.0.1:4545/referenceTypes.js").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); |