Skip to content

Commit fd0d0c9

Browse files
committed
e2e/badaml-{vuln,sandbox}: init
1 parent a74fada commit fd0d0c9

File tree

9 files changed

+242
-5
lines changed

9 files changed

+242
-5
lines changed

.github/workflows/e2e.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ jobs:
5151
TEST_NAME: ${{ inputs.test-name }}
5252
CONTRAST_GHCR_READ: ${{ secrets.CONTRAST_GHCR_READ }}
5353
DEBUG_SHELL: ${{ inputs.debug-shell }}
54-
SET: base
54+
SET: >-
55+
${{
56+
(inputs.test-name == 'badaml-sandbox' && 'badaml-sandbox') ||
57+
(inputs.test-name == 'badaml-vuln' && 'badaml-vuln') ||
58+
'base'
59+
}}
5560
steps:
5661
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5762
with:

.github/workflows/e2e_badaml.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: e2e test badaml
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- sets/badaml-vuln
7+
- sets/badaml-sandbox
8+
- packages/kata/kernel-uvm/**
9+
- packages/qemu-badaml/**
10+
- packages/badaml-payload/**
11+
- e2e/badaml-vuln/**
12+
- e2e/badaml-sandbox/**
13+
14+
jobs:
15+
tests:
16+
strategy:
17+
matrix:
18+
platform:
19+
- name: Metal-QEMU-SNP
20+
runner: SNP
21+
self-hosted: true
22+
- name: Metal-QEMU-TDX
23+
runner: TDX
24+
self-hosted: true
25+
- name: Metal-QEMU-SNP-GPU
26+
runner: SNP-GPU
27+
self-hosted: true
28+
- name: Metal-QEMU-TDX-GPU
29+
runner: TDX-GPU
30+
self-hosted: true
31+
test-name:
32+
- badaml-vuln
33+
- badaml-sandbox
34+
fail-fast: false
35+
name: "${{ matrix.platform.name }}"
36+
uses: ./.github/workflows/e2e.yml
37+
with:
38+
skip-undeploy: false
39+
test-name: ${{ matrix.test-name }}
40+
platform: ${{ matrix.platform.name }}
41+
runner: ${{ matrix.platform.runner }}
42+
self-hosted: ${{ matrix.platform.self-hosted }}
43+
debug-shell: true
44+
secrets:
45+
GITHUB_TOKEN_IN: ${{ secrets.GITHUB_TOKEN }}
46+
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
47+
NUNKI_CI_COMMIT_PUSH_PR: ${{ secrets.NUNKI_CI_COMMIT_PUSH_PR }}
48+
TEAMS_CI_WEBHOOK: ${{ secrets.TEAMS_CI_WEBHOOK }}
49+
CONTRAST_GHCR_READ: ${{ secrets.CONTRAST_GHCR_READ }}
50+
permissions:
51+
contents: read
52+
packages: write

.github/workflows/e2e_manual.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ on:
1111
# keep-sorted start
1212
- atls
1313
- attestation
14+
- badaml-sandbox
15+
- badaml-vuln
1416
- containerd-11644-reproducer
1517
- genpolicy-unsupported
1618
- gpu

.github/workflows/e2e_nightly.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ jobs:
5151
# keep-sorted start
5252
- atls
5353
- attestation
54+
- badaml-sandbox
55+
- badaml-vuln
5456
- containerd-11644-reproducer
5557
- genpolicy-unsupported
5658
- gpu
@@ -72,14 +74,14 @@ jobs:
7274
exclude:
7375
- platform:
7476
name: Metal-QEMU-SNP
75-
runner: SNP
76-
self-hosted: true
7777
test-name: gpu
7878
- platform:
7979
name: Metal-QEMU-TDX
80-
runner: TDX
81-
self-hosted: true
8280
test-name: gpu
81+
- debug-shell: false
82+
test-name: badaml-vuln
83+
- debug-shell: false
84+
test-name: badaml-sandbox
8385
fail-fast: false
8486
name: "${{ matrix.platform.name }}"
8587
uses: ./.github/workflows/e2e.yml

e2e/badaml-sandbox/badaml_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2026 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build e2e
5+
6+
package badamlsandbox
7+
8+
import (
9+
"flag"
10+
"os"
11+
"testing"
12+
13+
badamlvuln "github.com/edgelesssys/contrast/e2e/badaml-vuln"
14+
"github.com/edgelesssys/contrast/e2e/internal/contrasttest"
15+
)
16+
17+
func TestBadAMLVulnerability(t *testing.T) {
18+
badamlvuln.BadAMLTest(t, false)
19+
}
20+
21+
func TestMain(m *testing.M) {
22+
contrasttest.RegisterFlags()
23+
flag.Parse()
24+
os.Exit(m.Run())
25+
}

e2e/badaml-vuln/badaml.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2026 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build e2e
5+
6+
package badamlvuln
7+
8+
import (
9+
"context"
10+
"testing"
11+
"time"
12+
13+
"github.com/edgelesssys/contrast/e2e/internal/contrasttest"
14+
"github.com/edgelesssys/contrast/internal/kuberesource"
15+
"github.com/edgelesssys/contrast/internal/manifest"
16+
"github.com/edgelesssys/contrast/internal/platforms"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
// BadAMLTest is the shared logic between the badaml-vuln and badaml-sandbox tests.
21+
func BadAMLTest(t *testing.T, expectSuccessfulAttack bool) {
22+
platform, err := platforms.FromString(contrasttest.Flags.PlatformStr)
23+
require.NoError(t, err)
24+
25+
ct := contrasttest.New(t)
26+
27+
require.True(t, contrasttest.Flags.InsecureEnableDebugShell, "the --insecure-enable-debug-shell-access flag must be set to true to extract the initrd start address")
28+
29+
runtimeHandler, err := manifest.RuntimeHandler(platform)
30+
require.NoError(t, err)
31+
resources := kuberesource.CoordinatorBundle()
32+
resources = kuberesource.PatchRuntimeHandlers(resources, runtimeHandler)
33+
resources = kuberesource.AddPortForwarders(resources)
34+
ct.Init(t, resources)
35+
36+
require.True(t, t.Run("generate", ct.Generate), "contrast generate needs to succeed for subsequent tests")
37+
require.True(t, t.Run("apply", ct.Apply), "Kubernetes resources need to be applied for subsequent tests")
38+
if platform == platforms.MetalQEMUTDX {
39+
// TODO(katexochen): Set on TDX currently fails, as we are still measuring the ACPI tables, so
40+
// the injected BadAML table is detected during remote attestation. For TDX-GPU, we don't measure
41+
// the table so the attack works.
42+
require.True(t, t.Run("wait for debugshell", func(t *testing.T) {
43+
ctx, cancel := context.WithTimeout(t.Context(), ct.FactorPlatformTimeout(2*time.Minute))
44+
defer cancel()
45+
require.NoError(t, ct.Kubeclient.WaitForContainer(ctx, ct.Namespace, "coordinator-0", "contrast-debug-shell"))
46+
}), "debugshell start must succeed for subsequent tests")
47+
} else {
48+
require.True(t, t.Run("set", ct.Set), "contrast set needs to succeed for subsequent tests")
49+
}
50+
51+
var content string
52+
require.True(t, t.Run("get content of /run/deadbeef.bin", func(t *testing.T) {
53+
ctx, cancel := context.WithTimeout(t.Context(), ct.FactorPlatformTimeout(1*time.Minute))
54+
defer cancel()
55+
cmd := []string{"debugshell", `hexdump -e '1/1 "%02x"' -n4 /run/deadbeef.bin`}
56+
stdout, stderr, err := ct.Kubeclient.ExecContainer(ctx, ct.Namespace, "coordinator-0", "contrast-debug-shell", cmd)
57+
require.NoError(t, err, "running %q:\nstdout:\n%s\nstderr:\n%s", cmd, stdout, stderr)
58+
content = stdout
59+
}), "getting content of /run/deadbeef.bin needs to succeed for subsequent tests")
60+
61+
require.True(t, t.Run("get dmesg logs", func(t *testing.T) {
62+
ctx, cancel := context.WithTimeout(t.Context(), ct.FactorPlatformTimeout(1*time.Minute))
63+
defer cancel()
64+
cmd := []string{"debugshell", "dmesg | grep -i acpi"}
65+
stdout, stderr, err := ct.Kubeclient.ExecContainer(ctx, ct.Namespace, "coordinator-0", "contrast-debug-shell", cmd)
66+
require.NoError(t, err, "running %q:\nstdout:\n%s\nstderr:\n%s", cmd, stdout, stderr)
67+
t.Log(stdout)
68+
}))
69+
70+
name := "check attack is"
71+
if !expectSuccessfulAttack {
72+
name += " not"
73+
}
74+
name += " successful"
75+
require.True(t, t.Run(name, func(t *testing.T) {
76+
if expectSuccessfulAttack {
77+
require.Equal(t, "cafebabe", content, "the content of /run/deadbeef.bin should be 'cafebabe' if the attack was successful")
78+
} else {
79+
require.Equal(t, "deadbeef", content, "the content of /run/deadbeef.bin should be 'deadbeef' if the attack was not successful")
80+
}
81+
}))
82+
}

e2e/badaml-vuln/badaml_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2026 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build e2e
5+
6+
package badamlvuln
7+
8+
import (
9+
"flag"
10+
"os"
11+
"testing"
12+
13+
"github.com/edgelesssys/contrast/e2e/internal/contrasttest"
14+
)
15+
16+
func TestBadAMLVulnerability(t *testing.T) {
17+
BadAMLTest(t, true)
18+
}
19+
20+
func TestMain(m *testing.M) {
21+
contrasttest.RegisterFlags()
22+
flag.Parse()
23+
os.Exit(m.Run())
24+
}

e2e/internal/kubeclient/wait.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ func (c *Kubeclient) WaitForPod(ctx context.Context, namespace, name string) err
9898
return c.WaitForPodCondition(ctx, namespace, &singlePodReady{name: name})
9999
}
100100

101+
// WaitForContainer waits until a specific container in the named pod is ready.
102+
func (c *Kubeclient) WaitForContainer(ctx context.Context, namespace, podName, containerName string) error {
103+
return c.WaitForPodCondition(ctx, namespace, &containerReady{podName: podName, containerName: containerName})
104+
}
105+
101106
// WaitForJob waits until the Job succeeded.
102107
//
103108
// We consider the Job succeeded if the Pod belonging to the Job succeeded.
@@ -284,6 +289,44 @@ func (f *singlePodReady) String() string {
284289
return fmt.Sprintf("PodCondition(pod %s is ready)", f.name)
285290
}
286291

292+
// containerReady checks that a named container in a named pod is ready.
293+
type containerReady struct {
294+
podName string
295+
containerName string
296+
}
297+
298+
func (cr *containerReady) Check(lister listerscorev1.PodLister) (bool, error) {
299+
pods, err := lister.List(labels.Everything())
300+
if err != nil {
301+
return false, err
302+
}
303+
for _, pod := range pods {
304+
if pod.Name != cr.podName {
305+
continue
306+
}
307+
if pod.DeletionTimestamp != nil {
308+
return false, nil
309+
}
310+
for _, statuses := range [][]corev1.ContainerStatus{
311+
pod.Status.InitContainerStatuses,
312+
pod.Status.ContainerStatuses,
313+
pod.Status.EphemeralContainerStatuses,
314+
} {
315+
for _, cs := range statuses {
316+
if cs.Name == cr.containerName {
317+
return cs.Ready, nil
318+
}
319+
}
320+
}
321+
return false, nil
322+
}
323+
return false, nil
324+
}
325+
326+
func (cr *containerReady) String() string {
327+
return fmt.Sprintf("PodCondition(container %s in pod %s is ready)", cr.containerName, cr.podName)
328+
}
329+
287330
type oneRunning struct {
288331
ls labels.Selector
289332
}

packages/by-name/contrast/e2e/package.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ buildGoModule {
6363
# keep-sorted start
6464
"e2e/atls"
6565
"e2e/attestation"
66+
"e2e/badaml-sandbox"
67+
"e2e/badaml-vuln"
6668
"e2e/containerd-11644-reproducer"
6769
"e2e/genpolicy-unsupported"
6870
"e2e/gpu"

0 commit comments

Comments
 (0)