Skip to content

Commit 23f8c34

Browse files
authored
Add temporary support for preverified commands.
1 parent 53ae49a commit 23f8c34

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

crates/rrg/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ action-list_winreg_values = []
4545
action-list_winreg_keys = []
4646
action-query_wmi = []
4747
action-execute_signed_command = []
48+
# TODO: https://github.com/google/rrg/issues/137
49+
#
50+
# This feature exists to prevent preverified commands logic from being available
51+
# in most RRG builds. Once that mechanism is no longer needed, this should be
52+
# deleted.
53+
action-execute_signed_command-preverified = ["action-execute_signed_command"]
4854

4955
test-setfattr = []
5056
test-chattr = []

crates/rrg/src/action/execute_signed_command.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,50 @@ where
7373
use std::io::{Read as _, Write as _};
7474
use crate::request::ParseArgsError;
7575

76+
#[cfg(feature = "action-execute_signed_command-preverified")]
77+
{
78+
#[derive(Debug)]
79+
struct InvalidPreverifiedCommandsError(protobuf::Error);
80+
81+
impl std::fmt::Display for InvalidPreverifiedCommandsError {
82+
83+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84+
write!(f, "invalid preverified commands: {}", self.0)
85+
}
86+
}
87+
88+
impl std::error::Error for InvalidPreverifiedCommandsError {
89+
90+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91+
Some(&self.0)
92+
}
93+
}
94+
95+
#[derive(Debug)]
96+
struct PreverifiedCommandError;
97+
98+
impl std::fmt::Display for PreverifiedCommandError {
99+
100+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101+
write!(f, "no matching preverified command found")
102+
}
103+
}
104+
105+
impl std::error::Error for PreverifiedCommandError {
106+
}
107+
108+
use protobuf::Message as _;
109+
110+
let commands = rrg_proto::execute_signed_command::CommandList::parse_from_bytes({
111+
include_bytes!(env!("RRG_EXECUTE_SIGNED_COMMAND_PREVERIFIED"))
112+
}).map_err(|error| crate::session::Error::action(InvalidPreverifiedCommandsError(error)))?;
113+
114+
if !commands.commands().iter().any(|command| &args.raw_command == command) {
115+
return Err(ParseArgsError::invalid_field("command", PreverifiedCommandError).into());
116+
}
117+
}
118+
119+
#[cfg(not(feature = "action-execute_signed_command-preverified"))]
76120
match session.args().command_verification_key {
77121
Some(key) => key
78122
.verify_strict(&args.raw_command, &args.ed25519_signature)
@@ -333,6 +377,12 @@ impl crate::response::Item for Item {
333377
}
334378

335379
#[cfg(test)]
380+
// TODO: https://github.com/google/rrg/issues/137
381+
//
382+
// Most of these tests rely on custom commands which does not work with the
383+
// predefined mode. Once support for predefined commands is gone, we can enable
384+
// them for all builds again.
385+
#[cfg(not(feature = "action-execute_signed_command-preverified"))]
336386
mod tests {
337387

338388
use ed25519_dalek::Signer as _;
@@ -876,3 +926,75 @@ mod tests {
876926
assert_eq!(item.stdout, b"");
877927
}
878928
}
929+
930+
#[cfg(test)]
931+
#[cfg(feature = "action-execute_signed_command-preverified")]
932+
mod tests {
933+
use super::*;
934+
935+
// We just want to test preverification logic so we stick to Unix to keep
936+
// things simple and be able to rely on the `echo` command.
937+
#[cfg(target_family = "unix")]
938+
#[test]
939+
fn handle_all_preverified() {
940+
use protobuf::Message as _;
941+
942+
let mut commands = rrg_proto::execute_signed_command::CommandList::parse_from_bytes({
943+
include_bytes!(env!("RRG_EXECUTE_SIGNED_COMMAND_PREVERIFIED"))
944+
}).unwrap();
945+
946+
for raw_command in commands.take_commands() {
947+
let args = Args {
948+
raw_command,
949+
// In this test we use real raw preverified commands but only to
950+
// ensure that the verification lets it through. For actual exe-
951+
// ecution we just run `echo` (as this is safe and preverified
952+
// commands could have some dangerous stuff in there).
953+
path: "echo".into(),
954+
args: vec![String::from("foo")],
955+
env: std::collections::HashMap::new(),
956+
// Again, we provide a signature of just 0. This should not pass
957+
// the normal verification but we want to test that it is not
958+
// actually verified.
959+
ed25519_signature: ed25519_dalek::Signature::from_bytes({
960+
&[0; ed25519_dalek::Signature::BYTE_SIZE]
961+
}),
962+
stdin: Vec::from(b""),
963+
timeout: std::time::Duration::from_secs(5),
964+
};
965+
966+
let mut session = crate::session::FakeSession::new();
967+
handle(&mut session, args).unwrap();
968+
969+
assert_eq!(session.reply_count(), 1);
970+
971+
let item = session.reply::<Item>(0);
972+
assert!(item.exit_status.success());
973+
assert_eq!(item.stdout, b"foo\n");
974+
assert_eq!(item.stderr, b"");
975+
}
976+
}
977+
978+
#[test]
979+
fn handle_unverified() {
980+
use protobuf::Message as _;
981+
982+
let mut command = rrg_proto::execute_signed_command::Command::new();
983+
command.mut_path().set_raw_bytes(b"/usr/sbin/iamnotverified".to_vec());
984+
985+
let args = Args {
986+
raw_command: command.write_to_bytes().unwrap(),
987+
path: "/usr/sbin/iamnotverified".into(),
988+
args: vec![],
989+
env: std::collections::HashMap::new(),
990+
ed25519_signature: ed25519_dalek::Signature::from_bytes({
991+
&[0; ed25519_dalek::Signature::BYTE_SIZE]
992+
}),
993+
stdin: Vec::from(b""),
994+
timeout: std::time::Duration::from_secs(5),
995+
};
996+
997+
let mut session = crate::session::FakeSession::new();
998+
assert!(handle(&mut session, args).is_err());
999+
}
1000+
}

proto/rrg/action/execute_signed_command.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ message Command {
3333
}
3434
}
3535

36+
// TODO: https://github.com/google/rrg/issues/137
37+
//
38+
// This exists solely to support reading preverified commands from a file. Once
39+
// the mechanism of preverified commands is no longer needed, this should be
40+
// deleted.
41+
message CommandList {
42+
// Serialized `Command` messages.
43+
repeated bytes commands = 1;
44+
}
45+
3646
message Args {
3747
// Serialized `Command` message to execute.
3848
bytes command = 1;

0 commit comments

Comments
 (0)