I reviewed rust-ntpd 1.9.0-0ubuntu2 as checked into stonking. This shouldn't be
considered a full audit but rather a quick gauge of maintainability. The review
focused on the first-party Rust code (src/, ~56 findings); the 260 vendored
crates in rust-vendor/ were only assessed via scanner results and the
static-Built-Using mechanism.

rust-ntpd packages ntpd-rs, a memory-safe Rust implementation of the NTP (v4
and draft v5) and NTS (Network Time Security) protocols from the Pendulum
project. It runs as a systemd daemon under the dedicated ntpd-rs user,
defaulting to client-only synchronization against the Ubuntu NTP pool; server
and NTS-KE modes are opt-in via /etc/ntpd-rs/ntp.toml. Its attack surface is
untrusted network packet parsing (UDP 123, TLS-based NTS-KE), the ntp-ctl
control socket, and the optional Prometheus metrics HTTP endpoint. All Rust
dependencies are vendored; an Ubuntu patch switches NTS cryptography to the
system OpenSSL library.

- CVE History
  - Upstream has published 7 security advisories; all are
    denial-of-service class, none allow remote code execution or privilege
    escalation, and all are fixed well before the packaged 1.9.0
    - CVE-2023-33192 (High, CVSS 7.5, unvalidated NTS cookie length
      crashed the server, fixed in 0.3.3)
    - GHSA-37xq-q42p-rv3p (webpki startup DoS, fixed in 0.3.7)
    - CVE-2024-38528 (High, unlimited NTS-KE connections crash, fixed in
      1.1.3)
    - GHSA-v3mx-w872-mw68 (rustls crash as NTS-KE server, fixed in
      1.3.1)
    - GHSA-v83q-83hj-rw38 (client DoS via zero-sized/oversized NTS
      cookies, fixed in 1.5.0)
    - CVE-2025-58066 (Moderate, message storm between two servers, fixed
      in 1.6.2)
    - CVE-2026-26076 (Moderate, NTS cookie request CPU amplification,
      fixed in 1.7.1).
  - Upstream handling is good: coordinated GHSA advisories with same-day
    fixes for every issue, OpenSSF Best Practices and Scorecard badges, an
    external security audit report in docs/audits/, and production
    deployment by Let's Encrypt.
- Build-Depends
  - All build-deps are in main: debhelper, dh-apparmor, dh-cargo,
    dh-sequence-installsysusers, pkgconf, cargo/rustc (>= 1.88), and
    libssl-dev (OpenSSL 4.0.1).
  - The only external C library linked into the binaries is system OpenSSL
    (runtime NEEDED: libssl.so.4, libcrypto.so.4, plus libc/libm/libgcc_s);
    the vendored crypto stacks (ring, aws-lc-rs, openssl-src) are not
    compiled in thanks to debian/patches/openssl-crypto.patch. OpenSSL
    security fixes therefore flow through normal library updates without
    rebuilding this package.
  - All Rust dependencies are vendored and statically linked (260 crates,
    tracked via the Vendored-Sources-Rust field and debian/README.source);
    each is a security maintenance burden that the owning team has committed
    to handle via vendor refreshes and no-change rebuilds.
- pre/post inst/rm scripts
  - None
- init scripts
  - None
- systemd units
  - Both services run as dedicated unprivileged users (ntpd-rs and
    ntpd-rs-observe). The daemon only gets
    AmbientCapabilities=CAP_SYS_TIME (needed for clock steering) and
    CAP_NET_BIND_SERVICE (only used in opt-in server mode); the metrics
    exporter binds loopback only (127.0.0.1:9975).
  - Neither unit has systemd sandboxing directives (no ProtectSystem,
    PrivateTmp, NoNewPrivileges, CapabilityBoundingSet, ...); upstream is
    working on this (issue #2414). A daemon compromise is therefore bounded
    only by the unprivileged user, which is acceptable but should be
    hardened once upstream provides it.
  - The AppArmor profile for ntp-daemon ships in complain mode
    (flags=(complain, attach_disconnected)), so it logs violations but
    does not actually confine the daemon out of the box. Until systemd
    sandboxing lands (upstream issue #2414), the unprivileged service user
    is the only effective isolation.
- dbus services
  - None
- setuid binaries
  - None
- binaries in PATH
  - Installs ntp-daemon, ntp-ctl and ntp-metrics-exporter in /usr/bin.
    ntp-ctl only reads the daemon's observe socket and prints status;
    none perform dangerous operations.
- sudo fragments
  - None
- polkit files
  - None
- udev rules
  - None
- unit tests / autopkgtests
  - Tests run at build time and pass: 63 tests (56 library + 7 ntp-ctl
    integration), 0 failures; a failure fails the build.
  - Six non-trivial autopkgtests are declared, including an end-to-end NTS
    server/client exchange on loopback and a metrics-exporter HTTP check;
    the MIR team confirmed they pass on all supported architectures.
- cron jobs
  - None
- Build logs
  - Build succeeds with zero compiler errors; the 129 unique rustc warnings
    are all in vendored crates (105 of them test-only `unexpected_cfgs` in
    sharded-slab) while the first-party ntpd/ntp-proto crates compile
    warning-free.
  - Binaries are built with PIE, full RELRO, NX and LTO. Stack canaries and
    FORTIFY are absent, which is expected for Rust: the compiler does not
    emit the libc string functions those mechanisms instrument, and memory
    safety is enforced by the language.
  - No network access during the build beyond Ubuntu archive mirrors; all
    dependencies are vendored.

- Processes spawned
  - Only one runtime process spawn exists: force-sync runs `date -d
    @<timestamp>` for display (src/force_sync/mod.rs:51). The argument is
    internally computed so no injection is possible, but `date` is
    resolved via PATH; since force-sync is invoked by admins through
    sudo, a hostile PATH could fake the output. Trivial hardening
    candidate (absolute path), no current impact beyond printed output.
- Memory management
  - N/A
- File IO
  - None
    - All file access uses admin-configured paths with error mapping (NTS
      certificates/keys, keyset storage, log files).
    - NTS keysets are written with mode 0600 and the loader warns if
      existing key file permissions are too broad.
- Logging
  - None
- Environment variable usage
  - None
- Use of privileged functions
  - No setuid/setgid/chown/sudo/pkexec anywhere. 
  - Privileged ioctls (PTP_SYS_OFFSET, PPS, hardware timestamping) in
    vendored NTP-functional crates are expected for an NTP daemon and are
    bounded by the CAP_SYS_TIME capability granted in the systemd unit.
- Use of cryptography / random number sources etc
  - No first-party crypto code
- Use of temp files
  - None
- Use of networking
  - The untrusted-input surface (NTP v4/v5 and NTS-KE packet parsing) is
    implemented in memory-safe Rust with defensive size/magic validation
    before parsing, and socket error paths are handled without panics.
  - The observation socket uses an unlink-then-bind sequence with a small
    TOCTOU window (src/daemon/sockets.rs:60-96); the path is
    admin-configured inside a runtime directory owned by the
    unprivileged daemon user, so exploitation requires local control of
    that directory. Low severity.
  - The metrics exporter binds loopback (127.0.0.1:9975) by default and
    no cleartext HTTP endpoints exist.
- Use of WebKit
  - None
- Use of PolicyKit
  - None
- Utility Functions
  - None

- Any significant cppcheck results
  - None
- Any significant Coverity results
  - N/A
- Any significant shellcheck results
  - None. The 121 shellcheck notes are all in autopkgtest scripts
    (debian/tests/) and vendored upstream CI scripts that never run on
    user systems
- Any significant bandit results
  - None
- Any significant govulncheck results
  - N/A
- Any significant Semgrep results
  - None
- Any significant Rust code scan results
  - 24,754 hits; 3,110 unique findings after excluding test/doc paths and
    deduplication, of which 56 are first-party
    - clean first-party code: `#![forbid(unsafe_code)]` in the library and
      all three binaries
    - every production unwrap/expect call is a startup fail-fast
      invariant, none reachable from network input.
  - The 9,535 `unsafe` usages all sit in vendored crates (openssl, tokio,
    ring, zerocopy, ...), which is expected for crypto/FFI crates.
  - 1,242 TODO/FIXME markers across first-party and vendored code, these are
    maintainability signals only

The overall security posture is solid: first-party code is memory-safe Rust
with no unsafe blocks, TLS rides on the distro OpenSSL, services run
unprivileged with a clean CVE record of timely coordinated fixes, and all
scanners are quiet. rust-ntpd is a leaf package with no reverse dependencies,
so the static-vendoring blast radius is contained to this single source
package, which the owning team has explicitly committed to maintain through
vendor refreshes, no-change rebuilds and ESM. Since there's talk to make
ntpd-rs the default time synchronization solution in the long run, the
missing systemd sandboxing and, more importantly, the complain-mode
AppArmor profile need to be addressed before ntpd-rs takes on the default
time daemon role.

Security team ACK for promoting rust-ntpd to main. Recommended follow-ups
(not blocking):
- switch the AppArmor profile to enforce mode before ntpd-rs becomes the
  default time synchronization solution
- add systemd sandboxing once upstream issue #2414 lands


** CVE added: https://cve.org/CVERecord?id=CVE-2023-33192

** CVE added: https://cve.org/CVERecord?id=CVE-2024-38528

** CVE added: https://cve.org/CVERecord?id=CVE-2025-58066

** CVE added: https://cve.org/CVERecord?id=CVE-2026-26076

** CVE added: https://github.com/advisories/GHSA-37xq-q42p-rv3p

** CVE added: https://github.com/advisories/GHSA-v3mx-w872-mw68

** CVE added: https://github.com/advisories/GHSA-v83q-83hj-rw38

** Changed in: rust-ntpd (Ubuntu)
     Assignee: Ubuntu Security Team (ubuntu-security) => (unassigned)

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2166406

Title:
  [MIR] rust-ntpd

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/rust-ntpd/+bug/2166406/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to