Skip to content

Commit 23b1490

Browse files
committed
test(attestation): feature-gate the pre-launch-script rejection
The fixture's app-compose carries the pre-launch script that exported its signer key, which production verification rejects as arbitrary root code, so tests could never verify it. The new allow-pre-launch-script feature relaxes that single field: init_script and bash_script stay rejected, and the production wasm never enables it. The check takes the policy as an argument so the strict behavior keeps its unit test in test builds too.
1 parent bdcc550 commit 23b1490

4 files changed

Lines changed: 64 additions & 8 deletions

File tree

crates/attestation/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ edition = { workspace = true }
77
[features]
88
borsh-schema = ["borsh/unstable__schema", "tee-verifier-interface/borsh-schema"]
99
dstack-conversions = ["dep:dstack-sdk-types"]
10-
test-utils = []
10+
test-utils = ["allow-pre-launch-script"]
11+
# Accepts an app-compose carrying a `pre_launch_script`, which production rejects as
12+
# arbitrary root code. The test fixture needs one: it is how the node's in-enclave signer
13+
# key is exported during collection. `init_script` and `bash_script` stay rejected.
14+
allow-pre-launch-script = []
1115
# Pulls in `dcap-qvl` for full local DCAP + post-DCAP verification. Meant for
1216
# off-chain callers; `mpc-contract` enables it today.
1317
# TODO(#3264): contract drops this once DCAP moves to the verifier contract.

crates/attestation/src/attestation.rs

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,19 @@ impl DstackAttestation {
409409
&& app_compose.local_key_provider_enabled
410410
&& app_compose.allowed_envs.is_empty()
411411
&& app_compose.no_instance_id
412-
// Reject all three arbitrary-root-code fields. `pre_launch_script` and `init_script` run
413-
// unconditionally; `bash_script` only runs when `runner == "bash"` (so the runner pin
414-
// above already neutralizes it), but we reject it explicitly so the guarantee does not
415-
// silently depend on that pin.
416-
&& app_compose.pre_launch_script.is_none()
412+
&& Self::scripts_absent(app_compose)
413+
}
414+
415+
/// Rejects the arbitrary-root-code fields. `bash_script` only runs when
416+
/// `runner == "bash"`, but is rejected explicitly so the guarantee does not depend on
417+
/// the runner pin above.
418+
fn scripts_absent(app_compose: &AppCompose) -> bool {
419+
Self::scripts_absent_with(app_compose, cfg!(feature = "allow-pre-launch-script"))
420+
}
421+
422+
/// Takes the policy as an argument so tests can assert both, whatever features are on.
423+
fn scripts_absent_with(app_compose: &AppCompose, allow_pre_launch_script: bool) -> bool {
424+
(allow_pre_launch_script || app_compose.pre_launch_script.is_none())
417425
&& app_compose.init_script.is_none()
418426
&& app_compose.bash_script.is_none()
419427
}
@@ -620,19 +628,58 @@ mod tests {
620628
}
621629

622630
#[test]
623-
fn validate_app_compose_config__rejects_present_pre_launch_script() {
631+
fn scripts_absent_with__rejects_pre_launch_script_when_disallowed() {
632+
// Asserts the production policy, which test builds relax for the fixture.
633+
624634
// Given
625635
let app_compose = AppCompose {
626636
pre_launch_script: Some("echo pwn".to_string()),
627637
..valid_app_compose()
628638
};
629639
// When
630-
let result = DstackAttestation::validate_app_compose_config(&app_compose);
640+
let result = DstackAttestation::scripts_absent_with(&app_compose, false);
631641

632642
// Then
633643
assert!(!result)
634644
}
635645

646+
#[test]
647+
fn scripts_absent_with__accepts_pre_launch_script_when_allowed() {
648+
// Given
649+
let app_compose = AppCompose {
650+
pre_launch_script: Some("echo collecting fixtures".to_string()),
651+
..valid_app_compose()
652+
};
653+
// When
654+
let result = DstackAttestation::scripts_absent_with(&app_compose, true);
655+
656+
// Then
657+
assert!(result)
658+
}
659+
660+
#[test]
661+
fn scripts_absent_with__rejects_other_scripts_even_when_pre_launch_is_allowed() {
662+
// The relaxation must stay scoped to `pre_launch_script`.
663+
664+
// Given
665+
let with_init = AppCompose {
666+
init_script: Some("echo pwn".to_string()),
667+
..valid_app_compose()
668+
};
669+
let with_bash = AppCompose {
670+
bash_script: Some("echo pwn".to_string()),
671+
..valid_app_compose()
672+
};
673+
674+
// When
675+
let init_result = DstackAttestation::scripts_absent_with(&with_init, true);
676+
let bash_result = DstackAttestation::scripts_absent_with(&with_bash, true);
677+
678+
// Then
679+
assert!(!init_result);
680+
assert!(!bash_result);
681+
}
682+
636683
#[test]
637684
fn validate_app_compose_config__rejects_present_init_script() {
638685
// `init_script` is arbitrary root code run before dockerd. It is

crates/contract/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ bench-contract-methods = []
5959
# length of a fan-out queue). Distinct from `bench-contract-methods` because these are
6060
# behavioral hooks, not gas-measurement hooks.
6161
sandbox-test-methods = []
62+
# For the sandbox wasm of tests that submit the Dstack fixture and expect it to verify;
63+
# see `attestation/allow-pre-launch-script`.
64+
sandbox-test-attestation = ["mpc-attestation/allow-pre-launch-script"]
6265
dev-utils = ["rand", "threshold-signatures", "near-mpc-contract-interface/blstrs"]
6366
abi = [
6467
"borsh/unstable__schema",

crates/mpc-attestation/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ edition = { workspace = true }
88
abi = ["borsh/unstable__schema", "mpc-primitives/abi", "attestation/borsh-schema"]
99
dstack-conversions = ["attestation/dstack-conversions"]
1010
test-utils = ["attestation/test-utils"]
11+
# Forwards `attestation/allow-pre-launch-script`; see that crate for the rationale.
12+
allow-pre-launch-script = ["attestation/allow-pre-launch-script"]
1113
# Enables full local DCAP + post-DCAP verification, forwarding to
1214
# `attestation/local-verify` which pulls in `dcap-qvl`. Used off-chain (node,
1315
# tee-authority, attestation-cli) and, for now, by the contract's synchronous

0 commit comments

Comments
 (0)