mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
fix hole
This commit is contained in:
parent
a081ecc8c8
commit
5102c3ff64
18 changed files with 527 additions and 143 deletions
|
|
@ -22,6 +22,9 @@ toml = "0.8.19"
|
|||
clap = { version = "4.5.16", features = ["derive"] }
|
||||
postcard = { version = "1.0.10", features = ["alloc"] }
|
||||
libc = "0.2.158"
|
||||
seccompiler = "0.4.0"
|
||||
secrecy = { version = "0.8.0", features = ["serde"] }
|
||||
zeroize = "1.8.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ banner = "welcome to my server!!!\r\ni hope you enjoy your stay.\r\n"
|
|||
unprivileged_uid = 355353
|
||||
unprivileged_gid = 355353
|
||||
#unprivileged_user = "sshd"
|
||||
experimental_seccomp = true
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ pub struct SecurityConfig {
|
|||
pub unprivileged_gid: Option<u32>,
|
||||
/// The username of an unprivileged user.
|
||||
pub unprivileged_user: Option<String>,
|
||||
|
||||
/// Apply experimental seccomp filters.
|
||||
#[serde(default = "default_false")]
|
||||
pub experimental_seccomp: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
@ -72,6 +76,11 @@ fn default_true() -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
fn default_false() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
fn addr_default() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use std::{
|
||||
io,
|
||||
os::fd::{FromRawFd, OwnedFd},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{ready, Poll},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -18,8 +20,7 @@ use cluelessh_tokio::{
|
|||
};
|
||||
use eyre::{bail, ensure, Result, WrapErr};
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
io::{unix::AsyncFd, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
net::TcpStream,
|
||||
sync::mpsc,
|
||||
};
|
||||
|
|
@ -89,9 +90,9 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
|
|||
})
|
||||
})),
|
||||
auth_banner: config.auth.banner,
|
||||
sign_with_hostkey: Arc::new(move |msg| {
|
||||
do_key_exchange: Arc::new(move |msg| {
|
||||
let rpc_client = rpc_client3.clone();
|
||||
Box::pin(async move { rpc_client.sign(msg.hash, msg.public_key).await })
|
||||
Box::pin(async move { rpc_client.kex_exchange(msg).await })
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
@ -400,10 +401,8 @@ impl SessionState {
|
|||
|
||||
self.pty_term = Some(term);
|
||||
|
||||
self.writer = Some(Box::pin(File::from_std(std::fs::File::from(
|
||||
controller.try_clone()?,
|
||||
))));
|
||||
self.reader = Some(Box::pin(File::from_std(std::fs::File::from(controller))));
|
||||
self.writer = Some(Box::pin(AsyncFdWrapper::from_fd(controller.try_clone()?)?));
|
||||
self.reader = Some(Box::pin(AsyncFdWrapper::from_fd(controller)?));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -429,9 +428,9 @@ impl SessionState {
|
|||
fds.len()
|
||||
);
|
||||
|
||||
let stdin = File::from_std(std::fs::File::from(fds.remove(0)));
|
||||
let stdout = File::from_std(std::fs::File::from(fds.remove(0)));
|
||||
let stderr = File::from_std(std::fs::File::from(fds.remove(0)));
|
||||
let stdin = AsyncFdWrapper::from_fd(fds.remove(0))?;
|
||||
let stdout = AsyncFdWrapper::from_fd(fds.remove(0))?;
|
||||
let stderr = AsyncFdWrapper::from_fd(fds.remove(0))?;
|
||||
|
||||
self.writer = Some(Box::pin(stdin));
|
||||
self.reader = Some(Box::pin(stdout));
|
||||
|
|
@ -448,3 +447,73 @@ impl SessionState {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct AsyncFdWrapper {
|
||||
fd: AsyncFd<OwnedFd>,
|
||||
}
|
||||
|
||||
impl AsyncFdWrapper {
|
||||
fn from_fd(fd: OwnedFd) -> Result<Self> {
|
||||
rustix::io::ioctl_fionbio(&fd, true).wrap_err("putting fd into nonblocking mode")?;
|
||||
Ok(Self {
|
||||
fd: AsyncFd::new(fd).wrap_err("failed to register async event")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AsyncFdWrapper {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.fd.poll_read_ready(cx))?;
|
||||
|
||||
let unfilled = buf.initialize_unfilled();
|
||||
match guard.try_io(|inner| {
|
||||
rustix::io::read(inner.get_ref(), unfilled).map_err(io::Error::from)
|
||||
}) {
|
||||
Ok(Ok(len)) => {
|
||||
buf.advance(len);
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Ok(Err(err)) => return Poll::Ready(Err(err)),
|
||||
Err(_would_block) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for AsyncFdWrapper {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
loop {
|
||||
let mut guard = ready!(self.fd.poll_write_ready(cx))?;
|
||||
|
||||
match guard
|
||||
.try_io(|inner| rustix::io::write(inner.get_ref(), buf).map_err(io::Error::from))
|
||||
{
|
||||
Ok(result) => return Poll::Ready(result),
|
||||
Err(_would_block) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
_: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use cluelessh_keys::public::PublicKey;
|
|||
use cluelessh_keys::signature::Signature;
|
||||
use cluelessh_protocol::auth::CheckPubkey;
|
||||
use cluelessh_protocol::auth::VerifySignature;
|
||||
use cluelessh_transport::crypto::AlgorithmName;
|
||||
use eyre::bail;
|
||||
use eyre::ensure;
|
||||
use eyre::eyre;
|
||||
|
|
@ -26,6 +27,8 @@ use rustix::net::SendAncillaryBuffer;
|
|||
use rustix::net::SendAncillaryMessage;
|
||||
use rustix::net::SendFlags;
|
||||
use rustix::termios::Winsize;
|
||||
use secrecy::ExposeSecret;
|
||||
use secrecy::Secret;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::Interest;
|
||||
|
|
@ -36,20 +39,16 @@ use tracing::debug;
|
|||
use tracing::trace;
|
||||
use users::os::unix::UserExt;
|
||||
use users::User;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Request {
|
||||
// TODO: This is a bit... not good, it's not good.
|
||||
// It can be used to sign any arbitrary message, or any arbitary exchange!
|
||||
// I think we need to let the monitor do the DH Key Exchange.
|
||||
// Basically, it should generate the private key for the exchange (and give that to the client)
|
||||
// and then when signing, we compute the shared secret ourselves for the hash.
|
||||
// This should ensure that the connection process cannot sign anything except an SSH kex has
|
||||
// but only with our specific chosen shared secret, which should make it entirely useless for anything else.
|
||||
Sign {
|
||||
hash: [u8; 32],
|
||||
public_key: PublicKey,
|
||||
},
|
||||
/// Performs the key exchange by generating a private key, deriving the shared secret,
|
||||
/// computing the hash and signing it.
|
||||
/// This is combined into one operation to ensure that no signature forgery can happen,
|
||||
/// as the only thing we sign here is a hash, and this hash is guaranteed to contain
|
||||
/// some random bytes from us, making it entirely unpredictable and useless to forge anything.
|
||||
KeyExchange(KeyExchangeRequest),
|
||||
CheckPublicKey {
|
||||
user: String,
|
||||
session_identifier: [u8; 32],
|
||||
|
|
@ -76,6 +75,56 @@ enum Request {
|
|||
Wait,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct KeyExchangeRequest {
|
||||
pub client_ident: Vec<u8>,
|
||||
pub server_ident: Vec<u8>,
|
||||
pub client_kexinit: Vec<u8>,
|
||||
pub server_kexinit: Vec<u8>,
|
||||
pub eph_client_public_key: Vec<u8>,
|
||||
pub server_host_key: PublicKey,
|
||||
pub kex_algorithm: String,
|
||||
}
|
||||
|
||||
impl Debug for KeyExchangeRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("KeyExchangeRequest")
|
||||
.field("client_ident", &"[...]")
|
||||
.field("server_ident", &"[...]")
|
||||
.field("client_kexinit", &"[...]")
|
||||
.field("server_kexinit", &"[...]")
|
||||
.field("eph_client_public_key", &self.eph_client_public_key)
|
||||
.field("server_host_key", &self.server_host_key)
|
||||
.field("kex_algorithm", &self.kex_algorithm)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SerializableSharedSecret(Vec<u8>);
|
||||
impl zeroize::Zeroize for SerializableSharedSecret {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize()
|
||||
}
|
||||
}
|
||||
impl Debug for SerializableSharedSecret {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SerializableSharedSecret")
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl secrecy::CloneableSecret for SerializableSharedSecret {}
|
||||
impl secrecy::SerializableSecret for SerializableSharedSecret {}
|
||||
impl secrecy::DebugSecret for SerializableSharedSecret {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct KeyExchangeResponse {
|
||||
pub hash: [u8; 32],
|
||||
pub server_ephemeral_public_key: Vec<u8>,
|
||||
pub shared_secret: secrecy::Secret<SerializableSharedSecret>,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct PtyRequest {
|
||||
height_rows: u32,
|
||||
|
|
@ -94,12 +143,10 @@ struct ShellRequest {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
struct ShellRequestPty {
|
||||
term: String,
|
||||
}
|
||||
|
||||
type SignResponse = Signature;
|
||||
type VerifySignatureResponse = bool;
|
||||
type CheckPublicKeyResponse = bool;
|
||||
type ShellResponse = ();
|
||||
|
|
@ -142,7 +189,9 @@ impl Server {
|
|||
|
||||
pub async fn process(&mut self) -> Result<()> {
|
||||
loop {
|
||||
let (recv, fds) = receive_with_fds::<Request>(&self.server).await?;
|
||||
let (recv, fds) = receive_with_fds::<Request>(&self.server)
|
||||
.await
|
||||
.wrap_err("parsing request from client")?;
|
||||
ensure!(fds.is_empty(), "Client sent FDs in request");
|
||||
self.receive_message(recv).await?;
|
||||
}
|
||||
|
|
@ -152,20 +201,55 @@ impl Server {
|
|||
trace!(?req, "Received RPC message");
|
||||
|
||||
match req {
|
||||
Request::Sign { hash, public_key } => {
|
||||
Request::KeyExchange(req) => {
|
||||
let Some(private) = self
|
||||
.host_keys
|
||||
.iter()
|
||||
.find(|privkey| privkey.private_key.public_key() == public_key)
|
||||
.find(|privkey| privkey.private_key.public_key() == req.server_host_key)
|
||||
else {
|
||||
self.respond_err("missing private key".to_owned()).await?;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let signature = private.private_key.sign(&hash);
|
||||
let Some(kex_algorithm) =
|
||||
cluelessh_transport::crypto::kex_algorithm_by_name(&req.kex_algorithm)
|
||||
else {
|
||||
self.respond_err("invalid kex algorithm".to_owned()).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.respond::<SignResponse>(Ok(signature)).await?;
|
||||
let req = cluelessh_transport::server::KeyExchangeParameters {
|
||||
client_ident: req.client_ident,
|
||||
server_ident: req.server_ident,
|
||||
client_kexinit: req.client_kexinit,
|
||||
server_kexinit: req.server_kexinit,
|
||||
eph_client_public_key: req.eph_client_public_key,
|
||||
server_host_key_algorithm:
|
||||
cluelessh_transport::crypto::HostKeySigningAlgorithm::new(
|
||||
req.server_host_key,
|
||||
),
|
||||
kex_algorithm,
|
||||
};
|
||||
|
||||
let Ok(resp) = cluelessh_transport::server::do_key_exchange(
|
||||
req,
|
||||
private,
|
||||
&mut cluelessh_protocol::OsRng,
|
||||
) else {
|
||||
self.respond_err("key exchange failed".to_owned()).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let resp = KeyExchangeResponse {
|
||||
hash: resp.hash,
|
||||
server_ephemeral_public_key: resp.server_ephemeral_public_key,
|
||||
shared_secret: Secret::new(SerializableSharedSecret(
|
||||
resp.shared_secret.expose_secret().0.to_vec(),
|
||||
)),
|
||||
signature: resp.signature,
|
||||
};
|
||||
|
||||
self.respond::<KeyExchangeResponse>(Ok(resp)).await?;
|
||||
}
|
||||
Request::CheckPublicKey {
|
||||
user,
|
||||
|
|
@ -367,7 +451,8 @@ impl Server {
|
|||
resp: ResponseResult<T>,
|
||||
fds: &[BorrowedFd<'_>],
|
||||
) -> Result<()> {
|
||||
send_with_fds(&self.server, &postcard::to_allocvec(&resp)?, fds).await?;
|
||||
let data = Zeroizing::new(postcard::to_allocvec(&resp)?);
|
||||
send_with_fds(&self.server, &data, fds).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -379,9 +464,32 @@ impl Client {
|
|||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
pub async fn sign(&self, hash: [u8; 32], public_key: PublicKey) -> Result<Signature> {
|
||||
self.request_response::<SignResponse>(&Request::Sign { hash, public_key })
|
||||
.await
|
||||
pub async fn kex_exchange(
|
||||
&self,
|
||||
params: cluelessh_transport::server::KeyExchangeParameters,
|
||||
) -> Result<cluelessh_transport::server::KeyExchangeResponse> {
|
||||
let resp = self
|
||||
.request_response::<KeyExchangeResponse>(&Request::KeyExchange(KeyExchangeRequest {
|
||||
client_ident: params.client_ident,
|
||||
server_ident: params.server_ident,
|
||||
client_kexinit: params.client_kexinit,
|
||||
server_kexinit: params.server_kexinit,
|
||||
eph_client_public_key: params.eph_client_public_key,
|
||||
server_host_key: params.server_host_key_algorithm.public_key(),
|
||||
kex_algorithm: params.kex_algorithm.name().to_owned(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
Ok(cluelessh_transport::server::KeyExchangeResponse {
|
||||
hash: resp.hash,
|
||||
server_ephemeral_public_key: resp.server_ephemeral_public_key,
|
||||
shared_secret: cluelessh_transport::crypto::SharedSecret::new(
|
||||
cluelessh_transport::crypto::SharedSecretInner(
|
||||
resp.shared_secret.expose_secret().0.clone(),
|
||||
),
|
||||
),
|
||||
signature: resp.signature,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn check_public_key(
|
||||
|
|
@ -478,6 +586,8 @@ impl Client {
|
|||
}
|
||||
|
||||
async fn send_request(&self, req: &Request) -> Result<()> {
|
||||
trace!(?req, "Sending RPC request");
|
||||
|
||||
let data = postcard::to_allocvec(&req)?;
|
||||
|
||||
send_with_fds(&self.socket, &data, &[]).await?;
|
||||
|
|
@ -489,7 +599,7 @@ impl Client {
|
|||
) -> Result<(R, Vec<OwnedFd>)> {
|
||||
let (resp, fds) = receive_with_fds::<ResponseResult<R>>(&self.socket)
|
||||
.await
|
||||
.wrap_err("failed to recv")?;
|
||||
.wrap_err("parsing response from server")?;
|
||||
|
||||
trace!(?resp, ?fds, "Received RPC response");
|
||||
|
||||
|
|
@ -499,7 +609,15 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
const MAX_DATA_SIZE: usize = 4048;
|
||||
|
||||
async fn send_with_fds(socket: &UnixDatagram, data: &[u8], fds: &[BorrowedFd<'_>]) -> Result<()> {
|
||||
ensure!(
|
||||
data.len() <= MAX_DATA_SIZE,
|
||||
"Trying to send too much data: {} > {MAX_DATA_SIZE}",
|
||||
data.len()
|
||||
);
|
||||
|
||||
socket
|
||||
.async_io(Interest::WRITABLE, || {
|
||||
let mut space = [0; rustix::cmsg_space!(ScmRights(3))]; //we send up to 3 fds at once
|
||||
|
|
@ -520,7 +638,7 @@ async fn send_with_fds(socket: &UnixDatagram, data: &[u8], fds: &[BorrowedFd<'_>
|
|||
}
|
||||
|
||||
async fn receive_with_fds<R: DeserializeOwned>(socket: &UnixDatagram) -> Result<(R, Vec<OwnedFd>)> {
|
||||
let mut data = [0; 1024];
|
||||
let mut data = Zeroizing::new([0; MAX_DATA_SIZE]);
|
||||
let mut space = [0; rustix::cmsg_space!(ScmRights(3))]; // maximum size
|
||||
let mut cmesg_buf = RecvAncillaryBuffer::new(&mut space);
|
||||
|
||||
|
|
@ -528,7 +646,7 @@ async fn receive_with_fds<R: DeserializeOwned>(socket: &UnixDatagram) -> Result<
|
|||
.async_io(Interest::READABLE, || {
|
||||
rustix::net::recvmsg(
|
||||
socket,
|
||||
&mut [IoSliceMut::new(&mut data)],
|
||||
&mut [IoSliceMut::new(&mut *data)],
|
||||
&mut cmesg_buf,
|
||||
RecvFlags::empty(),
|
||||
)
|
||||
|
|
@ -538,7 +656,7 @@ async fn receive_with_fds<R: DeserializeOwned>(socket: &UnixDatagram) -> Result<
|
|||
|
||||
let mut fds = Vec::new();
|
||||
|
||||
let data = postcard::from_bytes::<R>(&data[..read.bytes]).wrap_err("invalid request")?;
|
||||
let data_parsed = postcard::from_bytes::<R>(&data[..read.bytes]).wrap_err("invalid request")?;
|
||||
|
||||
for msg in cmesg_buf.drain() {
|
||||
match msg {
|
||||
|
|
@ -547,5 +665,5 @@ async fn receive_with_fds<R: DeserializeOwned>(socket: &UnixDatagram) -> Result<
|
|||
}
|
||||
}
|
||||
|
||||
Ok((data, fds))
|
||||
Ok((data_parsed, fds))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
os::fd::RawFd,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
|
|
@ -10,9 +11,10 @@ use rustix::{
|
|||
process::WaitOptions,
|
||||
thread::{Pid, UnshareFlags},
|
||||
};
|
||||
use tracing::{debug, trace};
|
||||
use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule, TargetArch};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::SerializedConnectionState;
|
||||
use crate::{SerializedConnectionState, PRIVSEP_CONNECTION_RPC_CLIENT_FD, PRIVSEP_CONNECTION_STREAM_FD};
|
||||
|
||||
#[tracing::instrument(skip(state), ret)]
|
||||
pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> {
|
||||
|
|
@ -40,6 +42,10 @@ pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> {
|
|||
|
||||
rustix::thread::set_no_new_privs(true)?;
|
||||
|
||||
if state.config.security.experimental_seccomp {
|
||||
seccomp().wrap_err("setting up seccomp")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +78,8 @@ fn pipe() -> Result<(File, File)> {
|
|||
/// If this fails, there might be zombie child processes.
|
||||
/// Therefore, the caller must exit if this function fails.
|
||||
#[tracing::instrument]
|
||||
pub fn unshare_namespaces() -> Result<()> {
|
||||
fn unshare_namespaces() -> Result<()> {
|
||||
// TODO: respect unprivileged_uid config and stuff
|
||||
let (mut child_ready_read, mut child_ready_write) = pipe()?;
|
||||
let (mut uid_map_ready_read, mut uid_map_ready_write) = pipe()?;
|
||||
|
||||
|
|
@ -189,3 +196,86 @@ pub fn unshare_namespaces() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn seccomp() -> Result<()> {
|
||||
use seccompiler::{SeccompCmpArgLen as ArgLen, SeccompCmpOp as Op, SeccompCondition as Cond};
|
||||
|
||||
let arch = match std::env::consts::ARCH {
|
||||
"x86_64" => TargetArch::x86_64,
|
||||
"aarch64" => TargetArch::aarch64,
|
||||
arch => {
|
||||
warn!("Seccomp not supported for architecture ({arch})m skipping");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let limit_fd = |fd: RawFd| {
|
||||
SeccompRule::new(vec![Cond::new(
|
||||
0, // fd
|
||||
ArgLen::Dword,
|
||||
Op::Eq,
|
||||
fd as u64,
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let filter = SeccompFilter::new(
|
||||
vec![
|
||||
(libc::SYS_write, vec![]),
|
||||
(libc::SYS_epoll_create1, vec![]),
|
||||
(libc::SYS_eventfd2, vec![]),
|
||||
(libc::SYS_epoll_wait, vec![]),
|
||||
(libc::SYS_epoll_ctl, vec![]),
|
||||
(libc::SYS_fcntl, vec![]), // todo: restrict (72)
|
||||
(libc::SYS_socketpair, vec![]),
|
||||
(libc::SYS_sendmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],),
|
||||
(libc::SYS_recvmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)]),
|
||||
(libc::SYS_sendto, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]),
|
||||
(libc::SYS_recvfrom, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]),
|
||||
(libc::SYS_getrandom, vec![]),
|
||||
(libc::SYS_rt_sigaction, vec![]),
|
||||
(libc::SYS_rt_sigprocmask, vec![]),
|
||||
(libc::SYS_mmap, vec![]),
|
||||
(libc::SYS_munmap, vec![]),
|
||||
(libc::SYS_sched_getaffinity, vec![]),
|
||||
(libc::SYS_sigaltstack, vec![]),
|
||||
(libc::SYS_futex, vec![]),
|
||||
(libc::SYS_read, vec![]),
|
||||
(libc::SYS_mprotect, vec![]),
|
||||
(libc::SYS_rseq, vec![]),
|
||||
(libc::SYS_set_robust_list, vec![]),
|
||||
(libc::SYS_prctl, vec![]),
|
||||
(libc::SYS_close, vec![]),
|
||||
(libc::SYS_madvise, vec![]),
|
||||
(libc::SYS_exit, vec![]),
|
||||
(libc::SYS_exit_group, vec![]),
|
||||
(libc::SYS_sched_yield, vec![]),
|
||||
(
|
||||
libc::SYS_ioctl,
|
||||
vec![SeccompRule::new(vec![Cond::new(
|
||||
1, // op
|
||||
// dword for musl, qword for glibc :D.
|
||||
// but since FIONBIO is <u32::MAX, we can use dword.
|
||||
ArgLen::Dword,
|
||||
Op::Eq,
|
||||
libc::FIONBIO, // non-blocking
|
||||
)?])?],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
SeccompAction::KillProcess,
|
||||
SeccompAction::Allow,
|
||||
arch,
|
||||
)
|
||||
.wrap_err("creating seccomp filter")?;
|
||||
|
||||
let program: BpfProgram = filter.try_into().wrap_err("compiling seccomp filter")?;
|
||||
|
||||
debug!("Installing seccomp filter");
|
||||
seccompiler::apply_filter(&program).wrap_err("installing seccomp filter")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue