use crate::backend::{Backend, SandboxBackend};
use crate::lifecycle::{ExecResult, SandboxConfig};
use crate::policy::SandboxPolicy;
#[derive(Debug)]
pub struct SgxBackend {
config: SandboxConfig,
gramine_path: String,
}
impl SgxBackend {
pub fn new(config: &SandboxConfig) -> crate::Result<Self> {
let gramine_path = find_gramine().ok_or_else(|| {
crate::KavachError::BackendUnavailable("gramine-sgx not found".into())
})?;
if !std::path::Path::new("/dev/sgx_enclave").exists() {
return Err(crate::KavachError::BackendUnavailable(
"SGX device /dev/sgx_enclave not found".into(),
));
}
Ok(Self {
config: config.clone(),
gramine_path,
})
}
}
fn generate_manifest(config: &SandboxConfig, command: &str, workdir: &std::path::Path) -> String {
let enclave_size = config
.policy
.memory_limit_mb
.map(|mb| format!("{mb}M"))
.unwrap_or_else(|| "256M".into());
let script_path = workdir.join("task.sh");
let _ = std::fs::write(&script_path, format!("#!/bin/sh\n{command}\n"));
let mut env_lines = String::new();
env_lines.push_str("loader.env.PATH = \"/usr/local/bin:/usr/bin:/bin\"\n");
env_lines.push_str("loader.env.HOME = \"/tmp\"\n");
for (k, v) in &config.env {
env_lines.push_str(&format!("loader.env.{k} = \"{v}\"\n"));
}
format!(
r#"# Kavach SGX enclave manifest (auto-generated)
[libos]
entrypoint = "/bin/sh"
[loader]
entrypoint = "file:{{{{ gramine.libos }}}}"
argv = ["/bin/sh", "-c", "{command}"]
{env_lines}
[sgx]
enclave_size = "{enclave_size}"
max_threads = 4
edmm_enable = false
[fs]
mounts = [
{{ path = "/lib", uri = "file:{{{{ gramine.runtimedir() }}}}" }},
{{ path = "/usr", uri = "file:/usr" }},
{{ path = "/bin", uri = "file:/bin" }},
{{ path = "/tmp", uri = "file:/tmp", type = "tmpfs" }},
{{ path = "/work", uri = "file:{workdir}" }},
]
[[fs.trusted_files]]
uri = "file:{{{{ gramine.libos }}}}"
[[fs.trusted_files]]
uri = "file:/bin/sh"
"#,
command = command.replace('"', "\\\""),
enclave_size = enclave_size,
env_lines = env_lines,
workdir = workdir.display(),
)
}
#[async_trait::async_trait]
impl SandboxBackend for SgxBackend {
fn backend_type(&self) -> Backend {
Backend::Sgx
}
async fn exec(&self, command: &str, policy: &SandboxPolicy) -> crate::Result<ExecResult> {
let workdir = tempfile::tempdir()
.map_err(|e| crate::KavachError::CreationFailed(format!("SGX workdir: {e}")))?;
let manifest = generate_manifest(&self.config, command, workdir.path());
let manifest_path = workdir.path().join("task.manifest.sgx");
std::fs::write(&manifest_path, &manifest)
.map_err(|e| crate::KavachError::CreationFailed(format!("write manifest: {e}")))?;
let _ = policy;
let mut cmd = tokio::process::Command::new(&self.gramine_path);
cmd.arg(&manifest_path).current_dir(workdir.path());
for (k, v) in &self.config.env {
cmd.env(k, v);
}
crate::backend::exec_util::execute_with_timeout(
&mut cmd,
self.config.timeout_ms,
"gramine-sgx",
)
.await
}
async fn health_check(&self) -> crate::Result<bool> {
let output = tokio::process::Command::new(&self.gramine_path)
.arg("--version")
.output()
.await
.map_err(|e| crate::KavachError::ExecFailed(format!("SGX health: {e}")))?;
Ok(output.status.success())
}
async fn destroy(&self) -> crate::Result<()> {
Ok(())
}
}
fn find_gramine() -> Option<String> {
for name in &["gramine-sgx"] {
if crate::backend::which_exists(name) {
return Some((*name).to_string());
}
}
for path in &["/usr/local/bin/gramine-sgx", "/usr/bin/gramine-sgx"] {
if std::path::Path::new(path).exists() {
return Some((*path).to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_generation() {
let config = SandboxConfig::builder().backend(Backend::Sgx).build();
let manifest = generate_manifest(&config, "echo hello", std::path::Path::new("/tmp/test"));
assert!(manifest.contains("entrypoint"));
assert!(manifest.contains("enclave_size"));
assert!(manifest.contains("echo hello"));
}
#[test]
fn manifest_with_memory_limit() {
let mut config = SandboxConfig::builder().backend(Backend::Sgx).build();
config.policy.memory_limit_mb = Some(512);
let manifest = generate_manifest(&config, "ls", std::path::Path::new("/tmp"));
assert!(manifest.contains("512M"));
}
#[test]
fn manifest_with_env() {
let mut config = SandboxConfig::builder().backend(Backend::Sgx).build();
config.env.push(("MY_VAR".into(), "my_value".into()));
let manifest = generate_manifest(&config, "ls", std::path::Path::new("/tmp"));
assert!(manifest.contains("MY_VAR"));
assert!(manifest.contains("my_value"));
}
#[test]
fn find_gramine_returns_none_when_missing() {
let _ = find_gramine();
}
}