First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

127
SECURITY.md Normal file
View File

@@ -0,0 +1,127 @@
# 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.