1- # configfs-tsm shim (run unmodified TDX attestation binaries)
1+ # configfs-tsm shim
22
3- Some programs request a TDX quote through the ** standard Linux interfaces** —
4- ` configfs-tsm ` (` /sys/kernel/config/tsm/report/* ` , with ` inblob ` /` outblob ` ) and a
5- ` /dev/tdx-guest ` device — rather than through the dstack SDK / guest-agent socket.
6- A stock dstack CVM doesn't expose those kernel interfaces to app containers, so
7- such binaries fail out of the box.
3+ Some attestation binaries get their TDX quote through the kernel's ` configfs-tsm `
4+ files (` /sys/kernel/config/tsm/report/* ` — write ` inblob ` , read ` outblob ` ) instead
5+ of the dstack SDK. dstack doesn't expose those files to containers, so they fail.
86
9- This example ships a small ** sidecar ** that bridges the gap: it re-exposes the
10- guest-agent's ` GetQuote ` RPC under the configfs-tsm file ABI. Your app writes
11- ` report_data ` to ` inblob ` and reads the raw Intel DCAP TDX quote from ` outblob ` ,
12- exactly as against the kernel. No OS change, no FUSE, no privileged container; the
13- quote is the genuine hardware quote and ` report_data ` is forwarded byte-for-byte .
7+ This sidecar bridges them: it serves ` inblob ` / ` outblob ` from a shared volume and
8+ forwards each request to the guest-agent's ` GetQuote ` . The quote is the real
9+ hardware quote ( ` report_data ` passed through unchanged), so an unmodified binary
10+ works with only docker-compose changes — no OS change, no FUSE, no privileged
11+ container. CI publishes the image to ` ghcr.io/dstack-tee/dstack-tsm-shim ` .
1412
15- The image is built and published to GHCR by
16- [ ` build-tsm-shim.yml ` ] ( ../.github/workflows/build-tsm-shim.yml ) :
17- ` ghcr.io/dstack-tee/dstack-tsm-shim ` .
13+ ## Use it
1814
19- ## Try it
20-
21- ``` bash
22- phala deploy -n tsm-shim-example -c docker-compose.yaml
23- phala cvms logs < app_id> -c app
24- ```
25-
26- Expected ` app ` log:
27-
28- ```
29- quote length : 5010 bytes
30- quote header : 0400 (a TDX v4 quote starts with 0400)
31- report_data bound in quote: True
32- PASS - unmodified configfs-tsm app got a real TDX quote via the shim
33- ```
34-
35- ## Adopt it in your app
36-
37- Add the ` tsm-shim ` service and two volumes, then add the four lines marked ` (+) `
38- to your existing service:
15+ Add the sidecar, then point your app at it with the ` (+) ` lines:
3916
4017``` yaml
4118services :
@@ -48,7 +25,6 @@ services:
4825
4926 my-app :
5027 image : your-app:latest
51- # ... your existing config ...
5228 depends_on :
5329 tsm-shim :
5430 condition : service_healthy # (+) wait until the shim is ready
@@ -63,34 +39,20 @@ volumes:
6339 tsm-devstub : {}
6440` ` `
6541
66- If your binary **hard-codes** ` /sys/kernel/config/tsm/report`, mount the shared
67- volume there instead of using `TSM_REPORT_PATH` :
68- ` - tsm-report:/sys/kernel/config/tsm/report` . For production, pin the image by
69- digest (`ghcr.io/dstack-tee/dstack-tsm-shim@sha256:...`).
70-
71- # # How it works
42+ If your binary hard-codes ` /sys/kernel/config/tsm/report`, mount the volume there
43+ instead of setting `TSM_REPORT_PATH`. For production, pin the image by digest.
7244
73- ` inblob` /`outblob` are **named pipes (FIFOs)** in a shared volume; a read of
74- ` outblob` blocks until the quote is ready (configfs-tsm's write-then-read
75- contract). On an `inblob` write the shim `POST`s `/GetQuote` to
76- ` /var/run/dstack.sock` and writes the quote to `outblob`. The image reports
77- healthy only once both FIFOs exist, so the app gates on `service_healthy`. An
78- **empty `outblob` read means the quote failed** (the shim logs why).
45+ # # Try the demo
7946
80- ` /dev/tdx-guest` can't be created in another container from a sidecar, so an
81- empty volume is mounted there to satisfy the common "does the device exist?"
82- check (dstack permits mounting under `/dev`).
47+ ` ` ` bash
48+ phala deploy -n tsm-shim-example -c docker-compose.yaml
49+ phala cvms logs <app_id> -c app # expect PASS and a ~5 KB quote
50+ ` ` `
8351
84- # # Limitations
52+ # # Good to know
8553
86- - Covers the **configfs-tsm `inblob`/`outblob`** path (used by `go-configfs-tsm`,
87- recent `libtdx-attest`, etc.).
88- - Does **not** emulate the `/dev/tdx-guest` `TDX_CMD_GET_REPORT0` ioctl : it
89- returns a raw, locally-MAC'd TDREPORT, which dstack doesn't expose and which
90- can't be derived from a quote, so it isn't recoverable in userspace by any
91- shim. Binaries that drive the device by ioctl are out of scope.
92- - **Single in-flight requester** per shim — the shared `inblob`/`outblob` pair
93- can't correlate concurrent callers (the kernel gives each opener its own
94- ` report/<entry>/` ; this doesn't). Run one shim per app; the shim rejects an
95- ` inblob` write larger than 64 bytes (a sign of racing writers) rather than
96- return an ambiguous quote.
54+ - Covers the configfs-tsm `inblob`/`outblob` path (go-configfs-tsm, recent
55+ libtdx-attest). It does **not** handle the `/dev/tdx-guest` ioctl, which needs a
56+ raw TDREPORT that dstack doesn't expose.
57+ - One request at a time, one shim per app — a shared `inblob`/`outblob` can't tell
58+ concurrent callers apart. An empty `outblob` read means the quote failed.
0 commit comments