Commit 205f0dd
committed
fix: close what a second adversarial pass found
Three reviewers went over the previous round in isolated worktrees. Two of the
findings are attack surface the previous round introduced.
**`health_file` was an arbitrary-file read whose contents came back to an
anonymous caller.** `tokio::fs::File::open` follows symlinks and the agent runs
as root. The path is measured into the compose hash; what sits *at* that path
at runtime is not, and the docs tell the app to bind-mount the directory into a
container -- so the container can replace it with a symlink afterwards. The
two-line parser then quoted what it found: a line that did not parse as a
timestamp came back as `"...is not a unix time in seconds: <that line>"`, 64
bytes at a time, on the unauthenticated 0.0.0.0:8090 listener. The errno was an
oracle for any path on top of that. Opened `O_NOFOLLOW` now, refused unless it
is a regular file, and the contents are never quoted -- a verdict names the
rule that failed, which is what an operator needs and is not an exfiltration
channel.
**A FIFO at that path killed the agent's blocking pool, silently.**
`tokio::fs` opens on `spawn_blocking`, and a blocking task cannot be cancelled,
so `open(2)` on a reader-less FIFO parks that thread forever while the timeout
merely drops the future. One thread per refresh, 1 per 15s, pool dead in about
two hours -- after which no `tokio::fs` call in the root agent completes. The
watchdog would not catch it: it probes over HTTP and never touches the pool.
`O_NONBLOCK` makes that open return immediately. The test for it hangs if the
flag is removed, which is how it was confirmed.
**`COMPOSE_PROJECT_NAME` defeated the container path entirely.** Measured on
docker compose 5.1.4 and nerdctl 2.3.5: both honour that env var, and it
outranks the compose file's top-level `name:`. The app supplies it through
`.decrypted-env`, so deriving the project from the file was wrong exactly when
an app sets one -- an empty container list, which `judge` reads as "nothing
started yet", i.e. permanently unhealthy with a message saying the opposite.
`NERDCTL_NAMESPACE` had the same shape.
So `app-compose.sh` records the namespace and the project it actually used --
resolved by `docker compose config`, which applies the real precedence -- and
the agent reads that instead of guessing. Its absence is now meaningful too:
the file is written before anything starts, so "not there yet" is the boot
window rather than a guess that happens to find nothing. That also deletes the
YAML parsing this branch added, and its dependency: the compose file is no
longer parsed by the agent at all.
**The poll round had no budget.** Measured against a listener that accepts and
never answers: `ceil(n/16) * 2s`, so 256 mute instances make a 33s round, and
`MissedTickBehavior::Delay` then makes that the interval -- one tenant's mute
CVMs become every other tenant's detection latency, permanently, because being
marked unhealthy does not remove an instance from the target list. The round
now gets one interval and rotates where it starts, so nothing starves, and it
says how many it did not reach rather than truncating quietly.
The rest, smaller:
- The snapshot is rewritten in full whenever any verdict changes, so one
flapping instance rewrote the whole fleet's file every round -- ~39 KB/s at
2000 instances, paid by the operator. Debounced; losing 30s of it costs one
poll.
- `select_targets` held the routing mutex across `latest_handshakes`, which
clones every peer's key (3.9 ms at 5000 instances) and on a cold cache shells
out to `wg show` synchronously. Fetched before the lock now.
- `warn_all_unhealthy`'s rate limit was a process-global mutex taken on the
per-connection path, so one tenant's fail-open serialized against another
tenant's connections. The state moved into `ProxyStateMut`, which the caller
already holds.
- `sanitize` mixed character counts and byte counts three ways: a 400-character
multi-byte reason was cut to 512 bytes with no truncation marker. And
`char::is_control` is category Cc only, so U+2028, U+2029 and the bidi
overrides survived it -- U+2028 is a line break to most log viewers, which is
the log forging the function exists to prevent. Both copies now share
`dstack_types::sanitize_for_log`.
- `HealthStore::restore` could hand a verdict to an instance the record says is
not gated, leaving it out of rotation *and* out of the poll set with nothing
able to lift it. The record wins.
- `Info()` carries the whole app-compose, so 1 MiB was too tight for the
port-policy client; the guest-facing bound is sized for that call, in one
place both gateway clients go through. The VMM's vsock client to a guest
agent was unbounded and now is not -- that peer is a tenant's CVM and the
VMM is the control plane for every other tenant on the host.
- Deploy-time validation rejected the wrong-runner case but not the likelier
one: `enabled` with no `health_file` and no service declaring a
`healthcheck:`, which reports healthy forever.
- Go could not express `health_file: ""` the way the other three SDKs can, so
it hashed differently.
Tests for the four things a mutation showed nothing covered: the
`requirements.health_check.enabled` -> `RegisterCvmRequest` hop (the only hop
between the manifest and the gateway, and making it inert left all 95 tests
green), the two guest clients opting into the response bound,
`record_instance_health`'s return value (the sole trigger for the snapshot),
and the snapshot's wiring at both ends. Two more asserted a constant against
itself and now assert the literal `dstack` that `WorkingDirectory=/dstack`
implies.
Docs corrected where they were wrong rather than merely thin: `requirements`
only fails an older guest closed if the deployer writes `"manifest_version":
"3"` as a string, since `AppCompose` is not `deny_unknown_fields`; a
single-file bind mount plus write-then-rename cancel out, because the rename
never touches the inode the agent holds; and the changelog described a
transport-wide 1 MiB cap that was deliberately not what shipped.1 parent a1c2cad commit 205f0dd
23 files changed
Lines changed: 913 additions & 286 deletions
File tree
- docs
- dstack
- dstack-types/src
- dstack-util/src
- gateway/src
- main_service
- proxy
- guest-agent
- src
- guest-api/src
- http-client/src
- os/common/rootfs
- sdk
- go/dstack
- python
- src/dstack_sdk
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
| |||
125 | 131 | | |
126 | 132 | | |
127 | 133 | | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
132 | 156 | | |
133 | 157 | | |
134 | 158 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
559 | 616 | | |
560 | 617 | | |
561 | 618 | | |
| |||
2484 | 2541 | | |
2485 | 2542 | | |
2486 | 2543 | | |
| 2544 | + | |
| 2545 | + | |
| 2546 | + | |
| 2547 | + | |
| 2548 | + | |
| 2549 | + | |
| 2550 | + | |
| 2551 | + | |
| 2552 | + | |
| 2553 | + | |
| 2554 | + | |
| 2555 | + | |
| 2556 | + | |
| 2557 | + | |
| 2558 | + | |
| 2559 | + | |
| 2560 | + | |
| 2561 | + | |
| 2562 | + | |
| 2563 | + | |
| 2564 | + | |
| 2565 | + | |
| 2566 | + | |
| 2567 | + | |
| 2568 | + | |
| 2569 | + | |
| 2570 | + | |
| 2571 | + | |
| 2572 | + | |
| 2573 | + | |
| 2574 | + | |
| 2575 | + | |
| 2576 | + | |
| 2577 | + | |
| 2578 | + | |
| 2579 | + | |
| 2580 | + | |
| 2581 | + | |
| 2582 | + | |
| 2583 | + | |
| 2584 | + | |
| 2585 | + | |
| 2586 | + | |
| 2587 | + | |
| 2588 | + | |
| 2589 | + | |
| 2590 | + | |
| 2591 | + | |
| 2592 | + | |
| 2593 | + | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
| 2597 | + | |
| 2598 | + | |
| 2599 | + | |
| 2600 | + | |
| 2601 | + | |
| 2602 | + | |
| 2603 | + | |
| 2604 | + | |
| 2605 | + | |
| 2606 | + | |
| 2607 | + | |
| 2608 | + | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
21 | 38 | | |
22 | 39 | | |
23 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
474 | 474 | | |
475 | 475 | | |
476 | 476 | | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
483 | | - | |
484 | | - | |
485 | | - | |
486 | | - | |
| 477 | + | |
487 | 478 | | |
488 | 479 | | |
489 | 480 | | |
| |||
987 | 978 | | |
988 | 979 | | |
989 | 980 | | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
990 | 999 | | |
991 | 1000 | | |
992 | 1001 | | |
| |||
1043 | 1052 | | |
1044 | 1053 | | |
1045 | 1054 | | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
1046 | 1071 | | |
1047 | 1072 | | |
1048 | 1073 | | |
| |||
3521 | 3546 | | |
3522 | 3547 | | |
3523 | 3548 | | |
| 3549 | + | |
| 3550 | + | |
| 3551 | + | |
| 3552 | + | |
| 3553 | + | |
| 3554 | + | |
| 3555 | + | |
| 3556 | + | |
| 3557 | + | |
| 3558 | + | |
| 3559 | + | |
| 3560 | + | |
| 3561 | + | |
| 3562 | + | |
| 3563 | + | |
| 3564 | + | |
| 3565 | + | |
| 3566 | + | |
| 3567 | + | |
| 3568 | + | |
| 3569 | + | |
| 3570 | + | |
| 3571 | + | |
| 3572 | + | |
| 3573 | + | |
| 3574 | + | |
3524 | 3575 | | |
3525 | 3576 | | |
3526 | 3577 | | |
3527 | 3578 | | |
3528 | 3579 | | |
3529 | 3580 | | |
3530 | | - | |
| 3581 | + | |
3531 | 3582 | | |
3532 | 3583 | | |
3533 | 3584 | | |
| |||
3539 | 3590 | | |
3540 | 3591 | | |
3541 | 3592 | | |
| 3593 | + | |
| 3594 | + | |
| 3595 | + | |
| 3596 | + | |
| 3597 | + | |
| 3598 | + | |
| 3599 | + | |
| 3600 | + | |
| 3601 | + | |
| 3602 | + | |
| 3603 | + | |
| 3604 | + | |
| 3605 | + | |
| 3606 | + | |
| 3607 | + | |
| 3608 | + | |
| 3609 | + | |
| 3610 | + | |
| 3611 | + | |
| 3612 | + | |
| 3613 | + | |
| 3614 | + | |
| 3615 | + | |
| 3616 | + | |
| 3617 | + | |
| 3618 | + | |
| 3619 | + | |
| 3620 | + | |
| 3621 | + | |
| 3622 | + | |
| 3623 | + | |
| 3624 | + | |
| 3625 | + | |
| 3626 | + | |
| 3627 | + | |
| 3628 | + | |
| 3629 | + | |
3542 | 3630 | | |
3543 | 3631 | | |
3544 | 3632 | | |
| |||
0 commit comments