From da55d6f7cdfb335b9e42d943ebb5d547509cfdb6 Mon Sep 17 00:00:00 2001 From: J H <2364004+Blu-J@users.noreply.github.com> Date: Wed, 8 Mar 2023 14:50:56 -0700 Subject: [PATCH] feat: Add in the chmod + chown to libs::js_engine (#2185) * feat: Add in the chmod + chown to libs::js_engine * fix: Build --- libs/js_engine/src/artifacts/loadModule.js | 22 +++++ libs/js_engine/src/lib.rs | 94 ++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/libs/js_engine/src/artifacts/loadModule.js b/libs/js_engine/src/artifacts/loadModule.js index 6686c0d6d..140d610c2 100644 --- a/libs/js_engine/src/artifacts/loadModule.js +++ b/libs/js_engine/src/artifacts/loadModule.js @@ -108,6 +108,26 @@ const writeJsonFile = ( path, toWrite: JSON.stringify(toWrite), }); + +const chown = async ( + { + volumeId = requireParam("volumeId"), + path = requireParam("path"), + uid = requireParam("uid"), + } = requireParam("options"), +) => { + return await Deno.core.opAsync("chown", volumeId, path, uid); +}; + +const chmod = async ( + { + volumeId = requireParam("volumeId"), + path = requireParam("path"), + mode = requireParam("mode"), + } = requireParam("options"), +) => { + return await Deno.core.opAsync("chmod", volumeId, path, mode); +}; const readJsonFile = async ( { volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"), ) => JSON.parse(await readFile({ volumeId, path })); @@ -170,6 +190,8 @@ const input = Deno.core.opSync("get_input"); const variable_args = Deno.core.opSync("get_variable_args"); const setState = (x) => Deno.core.opSync("set_value", x); const effects = { + chmod, + chown, writeFile, readFile, writeJsonFile, diff --git a/libs/js_engine/src/lib.rs b/libs/js_engine/src/lib.rs index db9538ffc..a4e290b52 100644 --- a/libs/js_engine/src/lib.rs +++ b/libs/js_engine/src/lib.rs @@ -273,6 +273,8 @@ impl JsExecutionEnvironment { } fn declarations() -> Vec { vec![ + fns::chown::decl(), + fns::chmod::decl(), fns::fetch::decl(), fns::read_file::decl(), fns::metadata::decl(), @@ -379,7 +381,9 @@ mod fns { use std::cell::RefCell; use std::collections::BTreeMap; use std::convert::TryFrom; + use std::fs::Permissions; use std::os::unix::fs::MetadataExt; + use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::time::Duration; @@ -1183,6 +1187,96 @@ mod fns { Ok(()) } + #[op] + async fn chown( + state: Rc>, + volume_id: VolumeId, + path_in: PathBuf, + ownership: u32, + ) -> Result<(), AnyError> { + let sandboxed = { + let state = state.borrow(); + let ctx: &JsContext = state.borrow(); + ctx.sandboxed + }; + + if sandboxed { + bail!("Will not run chown in sandboxed mode"); + } + + let (volumes, volume_path) = { + let state = state.borrow(); + let ctx: &JsContext = state.borrow(); + let volume_path = ctx + .volumes + .path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id) + .ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?; + (ctx.volumes.clone(), volume_path) + }; + if volumes.readonly(&volume_id) { + bail!("Volume {} is readonly", volume_id); + } + let new_file = volume_path.join(path_in); + // With the volume check + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); + } + let output = tokio::process::Command::new("chown") + .arg("--recursive") + .arg(format!("{ownership}")) + .arg(new_file.as_os_str()) + .output() + .await?; + if !output.status.success() { + return Err(anyhow!("Chown Error")); + } + Ok(()) + } + #[op] + async fn chmod( + state: Rc>, + volume_id: VolumeId, + path_in: PathBuf, + mode: u32, + ) -> Result<(), AnyError> { + let sandboxed = { + let state = state.borrow(); + let ctx: &JsContext = state.borrow(); + ctx.sandboxed + }; + + if sandboxed { + bail!("Will not run chmod in sandboxed mode"); + } + + let (volumes, volume_path) = { + let state = state.borrow(); + let ctx: &JsContext = state.borrow(); + let volume_path = ctx + .volumes + .path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id) + .ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?; + (ctx.volumes.clone(), volume_path) + }; + if volumes.readonly(&volume_id) { + bail!("Volume {} is readonly", volume_id); + } + let new_file = volume_path.join(path_in); + // With the volume check + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); + } + tokio::fs::set_permissions(new_file, Permissions::from_mode(mode)).await?; + Ok(()) + } /// We need to make sure that during the file accessing, we don't reach beyond our scope of control async fn is_subset( parent: impl AsRef,