Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Documentation/docs/developer/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,45 @@ QEMU=/path/to/qemu make test-in-svsm TEST_ARGS='--nocc -- --no-netdev'
A list of parameters for `launch_guest.sh` is listed in the
[INSTALL.md](../installation/INSTALL.md) document.

## Attestation tests

Attestation can be tested in the same infrastructure by running the
attest-enabled test image together with `kbs-test` and `aproxy`.
Comment on lines +74 to +75

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm still confused, why you need to run kbs-test and aproxy for the tests you added.

Can you tell me which of the tests we're adding require communication with KBS?
Maybe I missed something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the unit tests in commits 2 and 3 require kbs-test or aproxy.

commit 1 (test-in-svsm-attest.sh) needs kbs-test and aproxy as it is an end-to-end integration test
that boots the SVSM test image on real SNP hardware with the attest feature enabled, connects it to a local kbs-test server via aproxy, and checks that pre-boot attestation completes successfully ("attestation successful" in output).

This is the part that addresses the kbs-test integration mentioned in the original issue.

The TESTING.md is documenting the integration script.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So commit 1 and commits 2/3 are completely unrelated, right?
Why putting them in a single PR ?


### Requirements

In addition to the requirements for in-SVSM tests, attestation tests require:

- The `kbs-test` server from [coconut-svsm/kbs-test](https://github.com/coconut-svsm/kbs-test)
- The `aproxy` binary (built automatically as part of the SVSM build)

### Running

Clone and build `kbs-test`:

```shell
git clone https://github.com/coconut-svsm/kbs-test.git ../kbs-test
```

Run attestation tests:

```shell
KBS_TEST_DIR=../kbs-test QEMU=/path/to/qemu \
make FEATURES_TEST=vtpm,virtio-drivers,block,attest \
TEST_IN_SVSM_SCRIPT=./scripts/test-in-svsm-attest.sh \
TEST_IN_SVSM_DEPS=aproxy \
test-in-svsm

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't reuse test-in-svsm target that is used to run rust test in the SVSM context.
This new test is more like scripts/test-qemu-nocc-svsm.sh, so if you want to add a target in the makefile, just define a new one, or just document how to run the script directly.

```

You can replace `KBS_TEST_DIR` with `KBS_TEST_BIN=/path/to/kbs-test` if you
already have a `kbs-test` binary built.

The test will:
1. Start a local `kbs-test` server
2. Start an `aproxy` instance
3. Run the SVSM tests with attestation enabled
4. Verify "attestation successful" appears in the output

## Miri

Miri is an Undefined Behavior detection tool for Rust. It can run binaries and
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ XBUILD_ARGS_TEST += --feature ${FEATURES_TEST}
endif

TEST_ARGS ?=
TEST_IN_SVSM_SCRIPT ?= ./scripts/test-in-svsm.sh
TEST_IN_SVSM_DEPS ?=

CARGO ?= cargo
CLIPPY_OPTIONS ?= --all-features
Expand Down Expand Up @@ -125,8 +127,9 @@ miri:

test-igvm: $(IGVM_TEST_FILES)

test-in-svsm: bin/coconut-test-qemu.igvm $(IGVMMEASUREBIN)
./scripts/test-in-svsm.sh $(TEST_ARGS)
test-in-svsm: $(IGVMMEASUREBIN) $(TEST_IN_SVSM_DEPS)
cargo xbuild $(XBUILD_ARGS_TEST) ./configs/test/qemu-test-target.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why removing the dep with bin/coconut-test-qemu.igvm and adding the step here to build it?

TEST_IGVM=$(CURDIR)/bin/coconut-test-qemu.igvm $(TEST_IN_SVSM_SCRIPT) $(TEST_ARGS)

test-in-hyperv: bin/coconut-test-hyperv.igvm

Expand Down Expand Up @@ -195,4 +198,4 @@ clean:

distclean: clean

.PHONY: test miri clean clippy bin/stage2.bin bin/svsm-kernel.elf bin/test-kernel.elf stage1_elf_trampoline distclean $(APROXYBIN) $(IGVM_FILES) $(IGVM_TEST_FILES)
.PHONY: test miri clean clippy bin/stage2.bin bin/svsm-kernel.elf bin/test-kernel.elf stage1_elf_full stage1_elf_trampoline stage1_elf_test distclean $(APROXYBIN) $(IGVM_FILES) $(IGVM_TEST_FILES) test-in-svsm

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why adding stage1_elf_full and stage1_elf_test ?

107 changes: 107 additions & 0 deletions kernel/src/attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,110 @@ fn hash(

try_to_vec(&sha.finalize()).or(Err(AttestationError::VecAlloc))
}

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @tylerfanelli on these tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests LGTM.

use super::*;
use alloc::vec;
use cocoon_tpm_tpm2_interface::{Tpm2bEccParameter, TpmBuffer};

fn make_ecc_point(x: &[u8], y: &[u8]) -> TpmsEccPoint<'static> {
TpmsEccPoint {
x: Tpm2bEccParameter {
buffer: TpmBuffer::Owned(x.to_vec()),
},
y: Tpm2bEccParameter {
buffer: TpmBuffer::Owned(y.to_vec()),
},
}
}

mod negotiation_hash {
use super::*;

/// hash() feeds NegotiationParams into SHA-512 in the order they
/// appear in `response.params`. The ordering matters because
/// the server dictates which fields contribute to the attestation
/// hash and in what order. These two tests verify that
/// [Challenge, EcPublicKeyBytes] and [EcPublicKeyBytes, Challenge]
/// produce different digests, therefore confirming the function
/// respects the param ordering from the negotiation response.
#[test]
fn challenge_then_ec_key() {
let challenge = vec![0xdd; 48];
let x = vec![0x10; 66];
let y = vec![0x20; 66];
let response = NegotiationResponse {
challenge: challenge.clone(),
params: vec![
NegotiationParam::Challenge,
NegotiationParam::EcPublicKeyBytes,
],
};
let pub_key = make_ecc_point(&x, &y);

let result = hash(&response, &pub_key).unwrap();

let mut sha = Sha512::new();
sha.update(&challenge);
sha.update(&x);
sha.update(&y);
let expected = sha.finalize();
assert_eq!(result, expected.as_slice());
}

#[test]
fn ec_key_then_challenge() {
let challenge = vec![0xee; 24];
let x = vec![0x30; 10];
let y = vec![0x40; 10];
let response = NegotiationResponse {
challenge: challenge.clone(),
params: vec![
NegotiationParam::EcPublicKeyBytes,
NegotiationParam::Challenge,
],
};
let pub_key = make_ecc_point(&x, &y);

let result = hash(&response, &pub_key).unwrap();

let mut sha = Sha512::new();
sha.update(&x);
sha.update(&y);
sha.update(&challenge);
let expected = sha.finalize();
assert_eq!(result, expected.as_slice());
}

/// Changing the param order must change the hash. This is a
/// security property: if the server negotiates a different param
/// list, the resulting attestation evidence must differ.
#[test]
fn different_order_produces_different_hash() {
let challenge = vec![0x42; 32];
let x = vec![0x01; 10];
let y = vec![0x02; 10];
let pub_key = make_ecc_point(&x, &y);

let response_chal_first = NegotiationResponse {
challenge: challenge.clone(),
params: vec![
NegotiationParam::Challenge,
NegotiationParam::EcPublicKeyBytes,
],
};
let response_key_first = NegotiationResponse {
challenge: challenge.clone(),
params: vec![
NegotiationParam::EcPublicKeyBytes,
NegotiationParam::Challenge,
],
};

let hash1 = hash(&response_chal_first, &pub_key).unwrap();
let hash2 = hash(&response_key_first, &pub_key).unwrap();
assert_ne!(hash1, hash2);
}
}
}
176 changes: 176 additions & 0 deletions scripts/test-in-svsm-attest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/bin/bash
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2026 Coconut-SVSM Authors
#
# Run in-SVSM tests with attestation enabled by starting a local kbs-test
# server and aproxy instance.

set -euo pipefail

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
SVSM_DIR="$SCRIPT_DIR/.."

: "${TEST_IGVM:=$SVSM_DIR/bin/coconut-test-qemu.igvm}"
: "${KBS_TEST_URL:=http://127.0.0.1:8080}"
# Test-only placeholder secret; not used in production.
: "${KBS_TEST_SECRET:=00112233445566778899aabbccddeeff}"
: "${KBS_TEST_STARTUP_TIMEOUT:=300}"
: "${APROXY_STARTUP_TIMEOUT:=30}"

declare -a LAUNCH_GUEST_ARGS
declare -a KBS_TEST_CMD

LAUNCH_GUEST_ARGS=()
KBS_TEST_CMD=()

while [[ $# -gt 0 ]]; do
case $1 in
--nocc)
echo "Attestation tests require SEV-SNP hardware and do not support --nocc."
exit 1
;;
--)
shift
while [[ $# -gt 0 ]]; do
LAUNCH_GUEST_ARGS+=("$1")
shift
done
;;
*)
echo "Invalid parameter $1"
exit 1
;;
esac
done

if [[ -n "${KBS_TEST_BIN:-}" ]]; then
KBS_TEST_CMD=("$KBS_TEST_BIN")
elif [[ -n "${KBS_TEST_DIR:-}" ]]; then
KBS_TEST_CMD=(cargo run --manifest-path "$KBS_TEST_DIR/Cargo.toml" --)
elif command -v kbs-test >/dev/null 2>&1; then
KBS_TEST_CMD=(kbs-test)
else
echo "Unable to find kbs-test. Set KBS_TEST_BIN or KBS_TEST_DIR."
exit 1
fi

for bin in "$SVSM_DIR/bin/igvmmeasure" "$SVSM_DIR/bin/aproxy"; do
if [[ ! -x "$bin" ]]; then
echo "Required executable not found: $bin"
exit 1
fi
done

if [[ ! -f "$TEST_IGVM" ]]; then
echo "Required test image not found: $TEST_IGVM"
exit 1
fi

TEST_DIR=$(mktemp -d -q)
KBS_LOG="$TEST_DIR/kbs-test.log"
APROXY_LOG="$TEST_DIR/aproxy.log"
SVSM_LOG="$TEST_DIR/test-in-svsm.log"
APROXY_SOCKET="$TEST_DIR/svsm-proxy.sock"
KBS_PID=0
APROXY_PID=0

cleanup() {
if [[ $APROXY_PID -ne 0 ]]; then
kill "$APROXY_PID" 2>/dev/null || true
fi
if [[ $KBS_PID -ne 0 ]]; then
kill "$KBS_PID" 2>/dev/null || true
fi
rm -rf "$TEST_DIR"
}
trap cleanup EXIT

wait_for_http() {
local url="$1"
local pid="$2"
local timeout="$3"
local deadline=$((SECONDS + timeout))
while (( SECONDS < deadline )); do
if curl -sS --max-time 1 "$url" >/dev/null 2>&1; then
return 0
fi
if ! kill -0 "$pid" >/dev/null 2>&1; then
return 1
fi
sleep 0.2
done
return 1
}

wait_for_socket() {
local socket="$1"
local pid="$2"
local timeout="$3"
local deadline=$((SECONDS + timeout))
while (( SECONDS < deadline )); do
if [[ -S "$socket" ]]; then
return 0
fi
if ! kill -0 "$pid" >/dev/null 2>&1; then
return 1
fi
sleep 0.2
done
return 1
}

MEASUREMENT=$("$SVSM_DIR/bin/igvmmeasure" "$TEST_IGVM" measure -b)

"${KBS_TEST_CMD[@]}" \
--measurement "$MEASUREMENT" \
--secret "$KBS_TEST_SECRET" >"$KBS_LOG" 2>&1 &
KBS_PID=$!

if ! wait_for_http "$KBS_TEST_URL" "$KBS_PID" "$KBS_TEST_STARTUP_TIMEOUT"; then
echo "Timed out waiting for kbs-test at $KBS_TEST_URL"
cat "$KBS_LOG"
exit 1
fi

"$SVSM_DIR/bin/aproxy" \
--protocol kbs \
--url "$KBS_TEST_URL" \
--unix "$APROXY_SOCKET" \
--force >"$APROXY_LOG" 2>&1 &
APROXY_PID=$!

if ! wait_for_socket "$APROXY_SOCKET" "$APROXY_PID" "$APROXY_STARTUP_TIMEOUT"; then
echo "Timed out waiting for aproxy socket: $APROXY_SOCKET"
cat "$APROXY_LOG"
exit 1
fi

set +e
TEST_IGVM="$TEST_IGVM" \
"$SCRIPT_DIR/test-in-svsm.sh" \
-- \
--aproxy "$APROXY_SOCKET" \
${LAUNCH_GUEST_ARGS[@]+"${LAUNCH_GUEST_ARGS[@]}"} 2>&1 | tee "$SVSM_LOG"
SVSM_EXIT=${PIPESTATUS[0]}
set -e

if [[ $SVSM_EXIT -ne 0 ]]; then
echo "SVSM test failed with status $SVSM_EXIT"
echo "--- aproxy log ---"
cat "$APROXY_LOG"
echo "--- kbs-test log ---"
cat "$KBS_LOG"
exit $SVSM_EXIT
fi

if ! grep -q "attestation successful" "$SVSM_LOG"; then
echo "Attestation success message not found in SVSM output"
echo "--- aproxy log ---"
cat "$APROXY_LOG"
echo "--- kbs-test log ---"
cat "$KBS_LOG"
exit 1
fi

echo "Attestation test passed"
5 changes: 3 additions & 2 deletions scripts/test-in-svsm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
set -e

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
: "${TEST_IGVM:=$SCRIPT_DIR/../bin/coconut-test-qemu.igvm}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this change is related?


test_io(){
PIPE_IN=$1
Expand All @@ -21,7 +22,7 @@ test_io(){
# 0x01: return SEV-SNP pre-calculated launch measurement (48 bytes)
"01")
$SCRIPT_DIR/../bin/igvmmeasure \
$SCRIPT_DIR/../bin/coconut-test-qemu.igvm measure -b \
"$TEST_IGVM" measure -b \
| xxd -r -p > $PIPE_IN
;;
# 0x02 Virtio-blk test: send md5 sum of svsm state image to SVSM.
Expand Down Expand Up @@ -67,7 +68,7 @@ while [[ $# -gt 0 ]]; do
done


$SCRIPT_DIR/launch_guest.sh --igvm $SCRIPT_DIR/../bin/coconut-test-qemu.igvm \
$SCRIPT_DIR/launch_guest.sh --igvm "$TEST_IGVM" \
--state "$TEST_DIR/svsm_state.raw" \
--unit-tests $TEST_DIR/pipe \
$LAUNCH_GUEST_ARGS "$@" || svsm_exit_code=$?
Expand Down