Keep keyring session payloads out of process arguments
This commit is contained in:
+47
-2
@@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
fs, io,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
pub const KEYRING_SERVICE: &str = "trixie.server.session";
|
||||
@@ -84,7 +85,34 @@ pub fn store_session(settings: &Settings, session: &str) -> io::Result<()> {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty session"));
|
||||
}
|
||||
let description = key_description(settings);
|
||||
keyctl(&["add", "user", &description, session, "@s"]).map(|_| ())
|
||||
write_key_payload(key_write_command(&description), session.as_bytes())
|
||||
}
|
||||
|
||||
// Secret payloads must never be arguments: process listings can expose argv.
|
||||
fn key_write_command(description: &str) -> Command {
|
||||
let mut command = Command::new("keyctl");
|
||||
command.args(["padd", "user", description, "@s"]);
|
||||
command
|
||||
}
|
||||
|
||||
fn write_key_payload(mut command: Command, payload: &[u8]) -> io::Result<()> {
|
||||
let mut child = command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?;
|
||||
let result = child.stdin.take().expect("piped stdin").write_all(payload);
|
||||
// Closing stdin above signals EOF. Reap the child even if writing failed.
|
||||
let status = child.wait()?;
|
||||
result?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
"Linux kernel keyring access failed",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_session(settings: &Settings) -> io::Result<Option<String>> {
|
||||
@@ -117,6 +145,23 @@ pub fn clear_session(settings: &Settings) -> io::Result<()> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn key_write_arguments_contain_only_metadata() {
|
||||
let command = key_write_command("test-description");
|
||||
let args: Vec<_> = command.get_args().collect();
|
||||
assert_eq!(args, ["padd", "user", "test-description", "@s"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_is_delivered_via_stdin_and_failures_are_reported() {
|
||||
let mut receiver = Command::new("sh");
|
||||
receiver.args(["-c", "test \"$(cat)\" = fixture-payload"]);
|
||||
write_key_payload(receiver, b"fixture-payload").unwrap();
|
||||
let mut failure = Command::new("sh");
|
||||
failure.args(["-c", "cat >/dev/null; exit 1"]);
|
||||
assert!(write_key_payload(failure, b"fixture-payload").is_err());
|
||||
}
|
||||
|
||||
fn settings() -> Settings {
|
||||
Settings {
|
||||
server_address: "https://trixie.example".into(),
|
||||
|
||||
Reference in New Issue
Block a user