Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement "unsafe" builtins #1766

Merged
merged 12 commits into from
Dec 6, 2024
12 changes: 7 additions & 5 deletions rust/crates/nasl-function-proc-macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl<'a> ArgsStruct<'a> {
self.args.iter().any(|arg| {
matches!(
arg.kind,
ArgKind::PositionalIterator | ArgKind::CheckedPositionalIterator
ArgKind::PositionalIterator(_) | ArgKind::CheckedPositionalIterator(_)
)
})
}
Expand Down Expand Up @@ -84,14 +84,16 @@ impl<'a> ArgsStruct<'a> {
_register
}
},
ArgKind::PositionalIterator => {
ArgKind::PositionalIterator(arg) => {
let position = arg.position;
quote! {
crate::nasl::utils::function::Positionals::new(_register)
crate::nasl::utils::function::Positionals::new(_register, #position)
}
}
ArgKind::CheckedPositionalIterator => {
ArgKind::CheckedPositionalIterator(arg) => {
let position = arg.position;
quote! {
crate::nasl::utils::function::CheckedPositionals::new(_register)?
crate::nasl::utils::function::CheckedPositionals::new(_register, #position)?
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/nasl-function-proc-macro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Error {
"Specific type specified in receiver argument. Currently, only `&self` is supported."
}
ErrorKind::WrongArgumentOrder => {
"Argument in wrong position. Order of arguments should be: Context/Register, Positionals, Named"
"Argument in wrong position. Order of arguments should be: Context/Register, Positionals, Named, *Positional list"
}
}.into()
}
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/nasl-function-proc-macro/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ impl Attrs {
return ArgKind::Register;
}
if ty_name_is(ty, "Positionals") {
return ArgKind::PositionalIterator;
return ArgKind::PositionalIterator(PositionalsArg { position });
}
if ty_name_is(ty, "CheckedPositionals") {
return ArgKind::CheckedPositionalIterator;
return ArgKind::CheckedPositionalIterator(PositionalsArg { position });
}
let attr_kind = self
.attrs
Expand Down
13 changes: 9 additions & 4 deletions rust/crates/nasl-function-proc-macro/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub enum ArgKind {
MaybeNamed(PositionalArg, NamedArg),
Context,
Register,
PositionalIterator,
CheckedPositionalIterator,
PositionalIterator(PositionalsArg),
CheckedPositionalIterator(PositionalsArg),
}

impl ArgKind {
Expand All @@ -69,8 +69,8 @@ impl ArgKind {
ArgKind::Positional(_) => 1,
ArgKind::MaybeNamed(_, _) => 2,
ArgKind::Named(_) => 3,
ArgKind::PositionalIterator => 4,
ArgKind::CheckedPositionalIterator => 4,
ArgKind::PositionalIterator(_) => 4,
ArgKind::CheckedPositionalIterator(_) => 4,
}
}
}
Expand All @@ -82,3 +82,8 @@ pub struct NamedArg {
pub struct PositionalArg {
pub position: usize,
}

pub struct PositionalsArg {
/// The position of the first argument in the iterator
pub position: usize,
}
4 changes: 4 additions & 0 deletions rust/src/nasl/builtin/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::http::HttpError;
use super::isotime::IsotimeError;
use super::knowledge_base::KBError;
use super::regex::RegexError;
use super::sys::SysError;
use super::{misc::MiscError, network::socket::SocketError, ssh::SshError, string::StringError};

#[derive(Debug, Error)]
Expand All @@ -36,6 +37,8 @@ pub enum BuiltinError {
Host(HostError),
#[error("{0}")]
Cert(CertError),
#[error("{0}")]
Sys(SysError),
#[cfg(feature = "nasl-builtin-raw-ip")]
#[error("{0}")]
RawIp(super::raw_ip::RawIpError),
Expand Down Expand Up @@ -81,6 +84,7 @@ builtin_error_variant!(RegexError, Regex);
builtin_error_variant!(KBError, KB);
builtin_error_variant!(HostError, Host);
builtin_error_variant!(CertError, Cert);
builtin_error_variant!(SysError, Sys);

#[cfg(feature = "nasl-builtin-raw-ip")]
builtin_error_variant!(super::raw_ip::RawIpError, RawIp);
2 changes: 2 additions & 0 deletions rust/src/nasl/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod regex;
mod report_functions;
mod ssh;
mod string;
mod sys;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -57,6 +58,7 @@ pub fn nasl_std_functions() -> Executor {
.add_set(description::Description)
.add_set(isotime::NaslIsotime)
.add_set(cryptographic::rc4::CipherHandlers::default())
.add_set(sys::Sys)
.add_set(ssh::Ssh::default())
.add_set(cert::NaslCerts::default());

Expand Down
162 changes: 162 additions & 0 deletions rust/src/nasl/builtin/sys/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use std::{
env, io,
path::{Path, PathBuf},
};

use thiserror::Error;
use tokio::process::Command;

use crate::nasl::prelude::*;

#[derive(Error, Debug)]
pub enum SysError {
#[error("Failed to spawn process. {0}")]
SpawnProcess(io::Error),
#[error("Unable to read file. {0}")]
ReadFile(io::Error),
#[error("Unable to read file metadata. {0}")]
ReadFileMetadata(io::Error),
#[error("Unable to write file. {0}")]
WriteFile(io::Error),
#[error("Unable to remove file. {0}")]
RemoveFile(io::Error),
#[error("Error while trying to find the path for the command '{0}'")]
FindCommandPath(String),
#[error("Command '{0}' not found.")]
CommandNotFound(String),
}

pub struct Sys;

async fn find_path_of_command(cmd: &str) -> Result<PathBuf, SysError> {
// Here, we use `which` to find out
// what the path of the command is.
let make_err = || SysError::FindCommandPath(cmd.to_string());
let mut which_cmd = Command::new("which");
let output = which_cmd.arg(cmd).output().await.map_err(|_| make_err())?;
if output.status.success() {
let stdout = String::from_utf8(output.stdout).map_err(|_| make_err())?;
let path = Path::new(&stdout);
let dir = path.parent().ok_or_else(make_err)?.to_owned();
Ok(dir)
} else {
Err(SysError::CommandNotFound(cmd.to_string()))
}
}

#[nasl_function(named(cd))]
async fn pread(
cmd: &str,
cd: Option<bool>,
argv: CheckedPositionals<String>,
) -> Result<String, FnError> {
let mut real_cmd = Command::new(cmd);
if let Some(true) = cd {
// If `cd` is true, we need to change the cwd to
// the path in which the executable that will be
// run resides.
let dir = find_path_of_command(cmd).await?;
real_cmd.current_dir(dir);
};
for arg in argv.iter() {
real_cmd.arg(arg);
}
let out = real_cmd.output().await.map_err(SysError::SpawnProcess)?;
let stdout = String::from_utf8(out.stdout).unwrap();
Ok(stdout)
}

#[nasl_function]
async fn find_in_path(cmd: &str) -> Result<bool, FnError> {
let result = find_path_of_command(cmd).await;
match result {
Ok(_) => Ok(true),
Err(SysError::CommandNotFound(_)) => Ok(false),
Err(e) => Err(e.into()),
}
}

#[nasl_function]
async fn fread(path: &Path) -> Result<String, FnError> {
tokio::fs::read_to_string(path)
.await
.map_err(|e| SysError::ReadFile(e).into())
}

#[nasl_function(named(data, file))]
async fn fwrite(data: &str, file: &Path) -> Result<usize, FnError> {
tokio::fs::write(file, data)
.await
.map_err(SysError::WriteFile)?;
let num_bytes = data.len();
Ok(num_bytes)
}

#[nasl_function]
async fn file_stat(path: &Path) -> Result<u64, FnError> {
let metadata = tokio::fs::metadata(path)
.await
.map_err(SysError::ReadFileMetadata)?;
Ok(metadata.len())
}

#[nasl_function]
async fn unlink(path: &Path) -> Result<(), FnError> {
tokio::fs::remove_file(path)
.await
.map_err(|e| SysError::RemoveFile(e).into())
}

#[nasl_function]
async fn get_tmp_dir() -> PathBuf {
env::temp_dir()
}

function_set! {
Sys,
async_stateless,
(
pread,
fread,
file_stat,
find_in_path,
fwrite,
get_tmp_dir,
unlink,
)
}

#[cfg(test)]
mod tests {
use crate::nasl::{builtin::sys::SysError, test_prelude::*};

#[tokio::test]
async fn pread() {
let mut t = TestBuilder::default();
t.ok(r#"pread("basename", "/a/b/c");"#, "c\n");
t.async_verify().await;
}

#[tokio::test]
async fn find_in_path() {
let mut t = TestBuilder::default();
t.ok(r#"find_in_path("basename");"#, true);
// Cannot think of a way to construct a command name here
// that is very unlikely to exist without it sounding ridiculous
t.ok(r#"find_in_path("foobarbaz");"#, false);
t.async_verify().await;
}

#[tokio::test]
async fn write_read_to_tmpdir() {
let mut t = TestBuilder::default();
t.run(r#"path = get_tmp_dir();"#);
t.run(r#"file = path + "/write_read_to_tmpdir";"#);
t.ok(r#"fwrite(file: file, data: "foo");"#, 3);
t.ok(r#"fread(file);"#, "foo");
t.ok(r#"file_stat(file);"#, 3);
t.run(r#"unlink(file);"#);
check_err_matches!(t, r#"file_stat(file);"#, SysError::ReadFileMetadata(_));
t.async_verify().await;
}
}
13 changes: 12 additions & 1 deletion rust/src/nasl/builtin/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ fn foo2(_register: &Register, x: usize) -> usize {
x
}

#[nasl_function]
fn add_positionals(_x: usize, _y: usize, argv: CheckedPositionals<usize>) -> usize {
argv.iter().sum()
}

struct Foo;

function_set! {
Foo,
sync_stateless,
(foo1, foo2)
(foo1, foo2, add_positionals)
}

/// Tests that the `Context` and `Register` arguments,
Expand All @@ -33,3 +38,9 @@ fn context_and_register_are_ignored_in_positional_index() {
t.ok("foo1(5);", 5);
t.ok("foo2(5);", 5);
}

#[test]
fn variadic_positionals_start_at_correct_index() {
let mut t = TestBuilder::default().with_executor(Executor::single(Foo));
t.ok("add_positionals(1, 2, 3, 4);", 7);
}
Loading
Loading