128 lines
6.3 KiB
Markdown
128 lines
6.3 KiB
Markdown
# Security policy
|
||
|
||
`ltk` is a UI toolkit for Wayland clients. It runs inside the trust boundary
|
||
of the application that links it: the toolkit decodes images, parses theme
|
||
JSON, lays out widget trees and drives a GLES / software renderer. A bug in
|
||
any of those paths can crash the host application or, in worst-case, expose
|
||
information that a co-tenant Wayland client should not have.
|
||
|
||
This document describes how to report a vulnerability and what we consider in
|
||
or out of scope.
|
||
|
||
## Supported versions
|
||
|
||
`ltk` is in active development. Security fixes land on the latest released
|
||
minor version on crates.io and on the `master` branch in this repository.
|
||
Older minor versions are not patched.
|
||
|
||
## Reporting a vulnerability
|
||
|
||
Email **info@liberux.net** with:
|
||
|
||
- a description of the issue,
|
||
- the smallest reproducer you can share (Rust source preferred),
|
||
- the `ltk` version and Rust toolchain you used,
|
||
- whether the issue is exploitable today or requires a follow-up condition.
|
||
|
||
Please do **not** open a public GitHub issue, pull request, or discussion for
|
||
unpatched vulnerabilities. We aim to acknowledge reports within 5 working
|
||
days and ship a fix within 30 days for high-severity issues. If you do not
|
||
hear back, escalate by sending a follow-up email.
|
||
|
||
You may use PGP if you prefer — request the current public key in your first
|
||
message and we will reply with it before sharing any sensitive details.
|
||
|
||
## In scope
|
||
|
||
- memory-safety bugs reachable from any public API on the `ltk` crate root
|
||
or its `core::UiSurface` runtime-free surface,
|
||
- panics or undefined behaviour triggered by a theme JSON document, a font
|
||
file, or an image buffer that an application would reasonably accept from
|
||
user content (e.g. a wallpaper picker, a notification icon),
|
||
- denial-of-service through unbounded memory growth in caches or layout
|
||
recursion that the toolkit performs on behalf of the application,
|
||
- credential leaks in widgets that expose a `secure` mode (currently
|
||
`TextEdit`) — for example, plaintext password residue in heap dumps after
|
||
the widget is dropped.
|
||
|
||
## Out of scope
|
||
|
||
- bugs that require a malicious Wayland compositor: ltk treats the
|
||
compositor as part of the trusted computing base and does not defend
|
||
against compositor-level spoofing, layer-shell privilege misuse, or
|
||
protocol-level attacks,
|
||
- bugs that require an attacker to set environment variables on the user's
|
||
process (`LTK_THEMES_DIR`, `LTK_FORCE_SOFTWARE`, `XDG_DATA_HOME`):
|
||
ltk is not designed to run inside setuid binaries or anywhere the env is
|
||
not under the user's control,
|
||
- bugs in third-party dependencies (`tiny-skia`, `fontdue`, `image`,
|
||
`smithay-client-toolkit`, …): report those upstream. We will track the
|
||
fix and bump the dependency in a release note.
|
||
|
||
## Hardening features
|
||
|
||
- `TextEdit::secure( true )` zeroizes the underlying string buffer on drop
|
||
via `secure_mem::secure_zero` (volatile writes + a `compiler_fence` so
|
||
the optimiser cannot elide the wipe). The guarantee covers two copies
|
||
the runtime owns: the `TextEdit` itself and the per-frame
|
||
`WidgetHandlers::TextEdit` snapshot the layout pass produces for input
|
||
dispatch — both implement `Drop` and run the wipe before the
|
||
allocation returns to the allocator. The next `view()` rebuild
|
||
replaces the previous frame's snapshot, so the typical lifecycle is
|
||
"one frame on the heap, then overwritten on drop". Single-line and
|
||
multiline are mutually exclusive with secure (passwords have no line
|
||
breaks); the text-input-v3 IME path is also skipped in secure mode so
|
||
preedit / commit strings never reach the compositor's IME stack.
|
||
|
||
### What `TextEdit::secure` does **not** cover
|
||
|
||
The wipe only reaches buffers ltk allocated. Callers retain
|
||
responsibility for everything outside the widget tree:
|
||
|
||
- **Application-owned `String`s.** `text_edit( "Pwd", &self.password )`
|
||
only borrows `self.password`; the credential lives on the consumer's
|
||
struct. Wipe it explicitly (via `secure_mem::secure_zero` on
|
||
`password.as_bytes_mut()`) once the auth handshake completes, or put
|
||
the credential in a wrapper type with a `Drop` impl that does the
|
||
wipe.
|
||
- **`on_change` callback copies.** Each keystroke fires a closure with
|
||
a fresh `String` clone of the current value. If the closure stores
|
||
or forwards that value (e.g. to a worker thread for PAM), each
|
||
stored copy is the consumer's responsibility — ltk no longer owns
|
||
it past the closure call.
|
||
- **OS-level disclosure surfaces.** Swap, hibernation images, and
|
||
core dumps are outside any user-space wipe's reach. For threat
|
||
models that require resistance to these, link an `mlock`-aware
|
||
allocator, restrict the process's `PR_SET_DUMPABLE` to `0`, and
|
||
consider running on a no-swap mount. ltk itself does not call
|
||
`mlock` because the buffers it owns are short-lived (single frame)
|
||
and `mlock`-ing them per-frame would dominate the cost without
|
||
materially improving the threat model.
|
||
- **Compositor-side state.** The compositor sees pointer / keyboard
|
||
events for the surface but never the rendered glyphs (since
|
||
`secure` paints bullets). Custom IMEs that the user has installed
|
||
can still observe keystrokes — that is an OS-level concern outside
|
||
ltk's trust boundary.
|
||
|
||
Login / lock-screen consumers should pair `TextEdit::secure` with an
|
||
explicit wipe of their own state on the success path. The
|
||
[`docs/cookbook.md`](./docs/cookbook.md) "Password field with PAM
|
||
submit" recipe shows the canonical shape: clear `self.password` after
|
||
spawning the worker thread so the in-flight clone in the worker is
|
||
the only remaining copy, and let the worker's drop-on-completion run
|
||
its own wipe.
|
||
- `draw_image_data` (both backends) refuses to upload a buffer whose length
|
||
does not match `width × height × 4`. The mismatch path logs once and
|
||
draws nothing.
|
||
- Glyph caches and gradient stops have soft caps to bound memory growth
|
||
under unusual content. Exceeding the cap drains older entries (glyphs)
|
||
or truncates the input (gradient stops) with a stderr warning.
|
||
|
||
## Supply chain
|
||
|
||
ltk targets the Rust toolchain shipped with Debian stable (currently 1.85).
|
||
Direct dependencies are pinned at the minor-version level except `image`,
|
||
which is pinned to an exact version because the crate's MSRV and feature
|
||
surface change between patch releases. We monitor RustSec advisories
|
||
manually; running `cargo audit` in your own CI is recommended.
|