1- do not depend on the libdrmtap-sys crate for the pin: its build.rs statically
compiles the whole libdrmtap C tree and a CAP_SYS_ADMIN helper and links
-ldrm/-lseccomp/-lcap, which defeats the runtime-dlopen model. keep drm a pure
dlopen backend and pin the .so by the build.py DRMTAP_REF release tag, guarded by
a strict vX.Y.Z regex. drops the now-moot Cargo.lock freshness CI checks.
2- render-node-less consumers no longer lose the stream: the --server signals
need_cpu on DrmStart when it cannot open a convert context, and the --service
streams the CPU-converted frame path for that connection instead of a dma-buf fd
the consumer cannot detile (which used to fall through to a PipeWire path nobody
can approve on an unattended seat).
3- mark PipeWire initialized only after every per-display capturer is created, so
a partial failure retries instead of the flag falsely reporting a complete init.
4- reject a degenerate (zero width/height) or short CPU frame before it reaches
PixelBuffer::new (which derives stride as data.len()/height, dividing by zero).
5- keep the export-ledger epoch at DRM_DISPLAY_GENERATION so a hotplug invalidates
cached buffers (elision stays off until the recycled-fb_id inode case is handled).
6- validate the udev uevent source (kernel nl_pid, multicast) with recvmsg so a
local process cannot unicast a spoofed drm-change event to the root listener.
move the egl detile and rgba pack out of the root --service and into the
unprivileged --server. the root now calls only drmtap_open + drmtap_grab_desc
and exports a raw dma-buf fd; the fd rides the _drm channel over SCM_RIGHTS with
a small descriptor (geometry, per-plane offsets/pitches, modifier, hdr) instead
of the full rgba frame, dropping the per-frame copy. the --server imports the fd
with drmtap_open_render + drmtap_convert_dmabuf, keyed by the import-once egl
cache, and the render context is created and dropped on the recv thread.
the _drm transport moves off Framed<BytesCodec> (which cannot carry a fd) to a
bespoke sendmsg/recvmsg framing (DrmConn) that attaches one SCM_RIGHTS cmsg only
when a fd is present and rejects a truncated ancillary message. the split
symbols are bound optionally so an older libdrmtap still loads the cpu path, and
the whole thing degrades to the cpu BGRA path or PipeWire when no render node is
available. pins libdrmtap-sys to =0.4.13 with the Cargo.lock checksum. folds in
the DP-MST, ldconfig-restart and per-display PipeWire-fallback review fixes and a
udev hotplug refresh.
adds an opt-in `drm` feature for unattended remote access on Wayland: it
captures below the compositor via libdrmtap, so there is no
xdg-desktop-portal consent dialog and it works at the login screen.
off by default. when the feature is off the build is byte-identical.
everything is gated behind feature = "drm" or lives only in the separate
rustdesk-unattended-wayland deb, whose package name is the informed consent.
architecture (agreed with the maintainer): the capture runs inside the root
--service, which already holds the privilege it needs, and streams frames to
the user --server over a service-scoped _drm ipc channel. libdrmtap is loaded
with dlopen at runtime (no link-time dependency, so the base build is
unchanged and it still runs on ubuntu 18), and the .so is built in ci from the
rustdesk-org/libdrmtap fork and shipped only in the drm deb. no setcap helper.
- service: DrmReader reads scanout directly via the dlopen loader; an
IpcDrmCapturer serves _drm consumers with a per-connection capture worker;
durable availability cache + pre-warm to avoid enumerate/re-probe restarts
- capture: multi-display (targets the selected crtc), hardware cursor over
_drm, transient-errno retry with a bounded stall, rejects non-32bpp scanouts
before the frame copy
- robustness: only active, crtc-bound outputs are offered (an unbound
crtc_id=0 connector is filtered and a client-selected 0 is refused, both
fall back to pipewire); a per-display rapid-rebuild guard demotes a flapping
display to pipewire; per-display (not global) zero-frame failure tracking
- root-service hardening: bounded frame allocation and a concurrent-connection
cap so a malformed scanout or a buggy consumer cannot OOM or thread-exhaust
the service; a negative availability verdict expires so displays that appear
after startup recover without a --server restart; exactly-one .so selection
in the packaging so a stale object is never silently shipped
- build: libdrmtap.so cloned at build time from rustdesk-org/libdrmtap main
and bundled only for the --drm deb; ci builds a separate
rustdesk-unattended-wayland deb (incl. an ubuntu 18.04 container)
- DRM_CAPTURE_SECURITY.md: threat model and hardening notes
* fix: refresh wayland uinput range on compositor layout change
The uinput absolute range is computed once at session init. If the
compositor layout changes mid-session (monitor scale or position
change, or a portal virtual output appearing once capture starts),
injected coordinates are rescaled by the stale range and land offset.
Poll the live desktop bounding box from the display service loop while
subscribed (one wayland roundtrip, throttled to 1.5s, no subprocesses)
and re-apply the uinput resolution when it changes. Also read a fresh
layout when computing the initial range in check_init, since the cache
is not cleared when a session closes through the restore-token path.
This is the X component of #15601. The stale advertised origins (the Y
component) are not touched here: re-advertising DisplayInfo mid-session
trips the portal re-negotiation and can drop displays.
Signed-off-by: Cody Harris <codyharris7188@gmail.com>
* fix: bound the mouse resolution IPC wait during session init
Wrap update_mouse_resolution in the same 3s timeout the periodic
refresh uses, so a hung IPC response can't stall check_init.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: build timeout future inside runtime, split linux lazy_static
Constructing the timeout future eagerly as the block_on argument panics
with 'there is no reactor running'; move it into the async block so it is
built inside the runtime context. Also move WAYLAND_UINPUT_RECT into its
own cfg-gated lazy_static block, an attribute on a single item inside the
shared block does not compile.
* fix: confirm uinput mouse device adopted new range before caching rect
send_refresh() now waits for the mouse service to ack that it recreated the
device with the new range instead of firing and forgetting, and
update_mouse_resolution() propagates that result. The layout poller only
caches the rect after the device actually adopts the range, so a failed
refresh errors and retries on the next check. The ack read is bounded by
IPC_REQUEST_TIMEOUT, matching the keyboard get-key-state path.
* fix: propagate refresh failures instead of caching a stale range
- input_service: error when the custom-mouse downcast fails so the poller
retries instead of caching an unconfirmed refresh
- uinput: on device recreation failure, keep the current device and the
IPC connection and withhold the ack so the client retries, instead of
killing the mouse handler
* fix: remap injected wayland coords onto the live layout after a monitor moves
The range refresh corrects the uinput ABS bounds, but a single-display client
sends whole-desktop coordinates offset by the origin of the display it follows,
taken from the layout advertised at session init. When another monitor is
rescaled or moved that origin shifts, so the coordinate lands offset before it
reaches uinput and the range refresh cannot recover it.
Snapshot the per-display layout at init, poll the live layout on the existing
1.5s throttle, and when they differ remap each injected move into the followed
display's current rectangle (matched by connector name, index fallback when the
compositor reports none). No-op and lock-free while the layout is unchanged.
---------
Signed-off-by: Cody Harris <codyharris7188@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
bindgen-0.65 is incompatible with llvm 22, we should upgrade to a
newer bindgen version
error message:
```
error[E0609]: no field `g_w` on type `vpx_codec_enc_cfg`
--> libs/scrap/src/common/vpxcodec.rs:66:19
|
66 | c.g_w = config.width;
| ^^^ unknown field
|
= note: available field is: `_address`
```
* harden wf_cliprdr.c
* fix copilot review
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix review
* fix review
* condense hardening comments, fix style in wf_cliprdr.c
Comment-only cleanup of the review-justification comments; also move
the mutex wait result declaration to the top of the block and fix
continuation-line indentation. No behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* add invariant tests for file contents request/response hardening
Cover the zeroed optional request fields, stream ID filtering,
oversized/NULL response rejection and the zero-byte EOF path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* address copilot review findings in wf_cliprdr.c
- Reject a negative FILECONTENTS_SIZE result: m_lSize is unsigned, so a
negative value became a huge bogus stream size that keeps reads going.
- Use a unique per-stream counter as the CLIPRDR streamId instead of a
truncated IStream pointer, which could collide or be reused after free
(and leaked heap addresses to the peer).
- Add req_f_request_mutex to serialize whole file-contents request/response
cycles, enforcing the previously assumed one-outstanding-request
invariant when multiple streams are read concurrently. Bounded acquire
so a wedged request fails the read instead of hanging a consumer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* serialize file-contents request state and poison streams after timeout
- Extract lock_mutex() for the WAIT_OBJECT_0/WAIT_ABANDONED idiom shared by
take_req_fdata, the request-serialization acquire, and the response handler.
- Collapse the acquire/send/take/release cycle into
cliprdr_request_filecontents_sync(), used by CliprdrStream_Read and the size
probe in CliprdrStream_New.
- Publish req_f_stream_id_expected/req_f_size_requested under req_f_mutex in the
sender and read them under the same lock in the response handler, removing the
cross-thread data race on those fields.
- Poison a stream (m_failed) after a request fails/times out, so a late response
carrying a previous offset's bytes cannot satisfy a later same-stream read.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* key the responder stream cache on connID as well as streamId
Per-stream ids restart from 1 in each peer process, so two connections can
emit the same streamId. The process-static pStreamStc cache keyed only on
streamId could then serve one peer the IStream cached for another peer (a
different file), silently returning wrong-file bytes. Add connID to the key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* harden the format-data path against late/duplicate responses
The format-data rendezvous had the same single-slot race the file-contents
path just fixed: the channel thread rewrote clipboard->hmem with no lock while
explorer-thread consumers read/freed it, nothing serialized concurrent
requests, and no flag told an expected response from a stray one.
- Add format_request_mutex (serializes the whole request/response cycle) and
hmem_mutex (guards the hmem hand-off and formatDataRespExpected).
- cliprdr_send_data_request now takes ownership of the response buffer under
hmem_mutex and returns it to the caller, so a later response cannot touch a
buffer a consumer is using. All three consumers (GetData, WM_RENDERFORMAT,
DELAYED_RENDERING) and the WM_CLIPBOARDUPDATE cleanup use the returned/taken
handle instead of the shared slot.
- The response handler drops any response arriving while formatDataRespExpected
is clear (late/duplicate/unsolicited), consumes the flag on the first
response, and no longer dereferences a NULL clipboard in the SetEvent path.
Pre-existing issue, not introduced by this branch; generalizes the
file-contents hardening to the format-data path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* remove the dedicated wf-cliprdr CI workflow
Drop .github/workflows/wf-cliprdr-ci.yml on this branch as requested.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* remove wf-cliprdr invariant tests
Drop tests/test_invariant_wf_cliprdr.c on this branch as requested.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor and simplify, remove mutex which is dangeours
* fix copilot false report
* fix review
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(clipboard): make CLIPRDR format-map growth checked
The Windows CLIPRDR format-list handler relies on map_ensure_capacity()
while processing peer-provided formats. The previous helper only attempted
growth: if realloc() failed, it returned silently and the caller continued
processing. A later iteration could then index past the allocated
format_mappings array.
Make format-map growth a checked operation. The handler now validates the
peer-provided format count, ensures the mapping array is large enough before
writing entries, and aborts processing if growth fails. Newly allocated slots
are zeroed so existing cleanup can safely run after partial processing.
Also bound remote format names before measuring/converting them. The chosen
limits follow Windows clipboard/atom constraints:
- registered clipboard format IDs use 0xC000..0xFFFF
- string atom names are limited to 255 bytes
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): reject invalid remote format-list entries
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): clean up stale Linux FUSE mounts
Recover Linux file clipboard FUSE mount points before remounting and stop treating a cached
context as valid when the mount has already gone away.
This fixes the desktop file manager copy failure that shows dialogs such as
"Error while copying a" and "There was an error copying the file into xxx".
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): fuse, reduce dups
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix: clear Linux file clipboard before unmounting FUSE
Ensure Linux client teardown clears RustDesk file clipboard URLs while
the FUSE context is still available. Also prefer fusermount before
umount to avoid noisy unprivileged teardown attempts.
Signed-off-by: fufesou <linlong1266@gmail.com>
* fix(clipboard): return and log errors
Signed-off-by: fufesou <linlong1266@gmail.com>
---------
Signed-off-by: fufesou <linlong1266@gmail.com>
* Support controller user attribution in audit logs
This PR supports associating audit logs with the controller user.
## Implementation:
- Add `ControlledContext { conn_audit_token }` to `PunchHole`, `RequestRelay`, and `FetchLocalAddr`.
- The server sends a controller-user identity snapshot to the controlled client through rendezvous messages.
- The controlled client sends the token back to the server when posting the `on_open` conn audit or IP whitelist alarm audit.
- This lets the server attach the controller user to audit logs.
## How the controlled client helps identify the controller user:
- Conn audit: sends the token to the server in `on_open`; the server creates the audit log and caches the user snapshot.
- File audit: sends `id` and `conn_id`; the server uses them to find the cached user snapshot.
- Alarm audit: IP whitelist sends the token directly; other alarm logs send `id` and `conn_id`, and the server uses them to find the cached user
snapshot.
## Compatibility:
- Supported only for logs created with a new server and a new controlled client.
- Does not require upgrading the controller client.
## Test
- [x] New/old clients connected to new/old servers, and conn/file/alarm audit logs worked normally.
- [x] New client connected to new server generated searchable conn/file/alarm audit logs.
- [x] Punch hole, local addr, and relay paths worked with audit logs and control role on new/old servers.
- [x] Direct IP connections produced audit logs, but do not support user audit.
Signed-off-by: 21pages <sunboeasy@gmail.com>
* rename conn_audit_token to conn_audit_ref
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
* fix(clipboard): unix, refresh cached file size/mtime on re-copy
sync_files() deduped re-copies by path string only, so editing a file
and re-copying it (same path) skipped refreshing the cached size/mtime
and the file-group descriptor; the peer then received the file
truncated to the old cached size (silent corruption for PDF/zip/pptx).
Widen the early-return guard to also compare a top-level (size, mtime)
fingerprint and to always rebuild when a directory is selected. The
Windows wf_cliprdr.c path re-stats per request and is unaffected.
Signed-off-by: RAIT-09 <51452399+RAIT-09@users.noreply.github.com>
* opt(clipboard): unix, compute file fingerprint once and pass into sync_files
fingerprint() was computed before taking the CLIP_FILES lock and then
recomputed inside ClipFiles::sync_files under the lock. Pass the precomputed
value in so the top-level stat runs once and outside the critical section.
No behavior change.
Signed-off-by: RAIT-09 <51452399+RAIT-09@users.noreply.github.com>
---------
Signed-off-by: RAIT-09 <51452399+RAIT-09@users.noreply.github.com>
* Add initial arm64 build logic
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Upgrade Flutter to 3.44.0 and introduce Windows arm64 in CI
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Bump bridge build to Flutter 3.44 as well
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Fix install flutter step for Win arm64
* Bump install-llvm-action to v2 for arm64 support
* Fix libsodium logic to only install through vcpkg on win arm64
* Fix Flutter installations on Win
* Flutter XCode: only build the current arch as it defaults to universal
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Ensure that we really have arm64 Dart + Flutter engine in CI
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Enable hwcodec feature now that upstream supports building it
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* CI: improve logic for getting Flutter arm64 SDK
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
* Apply PR feedback (only bump Flutter version on Win arm64)
* Exclude MSI build on arm64
* CI: build the MSI for Windows arm64 (WiX v4 ARM64 platform + native CustomActions)
* Address PR feedback
* Update Cargo.toml
* Update Cargo.lock
* Update Cargo.lock
* Add Flutter 3.44 DialogThemeData background colors
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: Dennis Ameling <dennis@dennisameling.com>
Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
On Wayland compositors whose xdg-desktop-portal backend exposes screencast
frames as DMA-BUF buffers — notably xdg-desktop-portal-cosmic 0.1.0 on
Pop!_OS 24.04 / COSMIC — inbound screen capture fails. PipeWireRecorder
links pipewiresrc directly to an appsink whose caps only accept
video/x-raw BGRx/RGBx in system memory. That format set is too narrow for
the portal's buffer-type / modifier negotiation, which collapses with:
pw.link: negotiating -> error no more output formats (-22)
gstpipewiresrc: stream error: no more output formats
gstbasesrc: streaming stopped, reason not-negotiated (-4)
ERROR src/server/wayland.rs: Failed scrap Element failed to change its state
Inserting a videoconvert element between pipewiresrc and appsink widens
the negotiable format set to any system-memory video/x-raw format, giving
the portal room to settle on a format it can deliver via its SHM path.
videoconvert then converts to the BGRx/RGBx the appsink expects.
Verified on Pop!_OS 24.04 / COSMIC with gst-launch, before and after:
# fails (current behaviour):
gst-launch-1.0 pipewiresrc path=N ! video/x-raw,format=BGRx ! fakesink
# works (with this change):
gst-launch-1.0 pipewiresrc path=N ! videoconvert ! video/x-raw,format=BGRx ! fakesink
After the change, inbound connections capture and stream the desktop
normally and the "Failed scrap" error no longer occurs.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(linux): enable mouse side buttons in remote sessions
Flutter's Linux embedder never delivers X11 button 8/9 (back/forward)
events to Dart, so mouse side buttons were silently dropped in remote
sessions.
Intercept these buttons at the GDK level via button-press/release-event
handlers on all windows (main + sub-windows) and forward them through
a dedicated platform channel to the active InputModel session.
Also add a defensive XSetPointerMapping call during enigo init to
extend the X11 core pointer button map to 9 buttons on servers where
it is smaller (e.g. minimal X server configurations).
* fix: address review feedback for side button support
- Use XOpenDisplay/XCloseDisplay instead of reading Display* from
xdo_t's private struct layout at offset 0 (fragile ABI assumption)
- Track side button down ownership per button via a Map instead of a
single slot, preventing cross-button mismatch on overlapping presses
* fix: gate side buttons on view-only and fix teardown
- Skip side button events in view-only sessions (consistent with
other mouse entry points)
- Release held side buttons on session close to avoid stuck buttons
on the remote
- Drop unpaired 'up' events instead of falling back to the active
model, which could send to the wrong session
* docs: add clarifying comments from review feedback
- Note global scope of XSetPointerMapping and that it runs once
via lazy_static singleton
- Clarify sub-window callback is safe on X11-only builds
- Document per-isolate design of initSideButtonChannel
* fix: replace broken XSetPointerMapping with diagnostic check
XSetPointerMapping requires the length to match XGetPointerMapping's
return value - it cannot extend the button count. The previous code
would trigger a BadValue X error on servers with fewer than 9 buttons.
Replace with a diagnostic-only check that logs whether the core
pointer has enough buttons for side button simulation. RustDesk's
uinput "Mouse passthrough" device already provides the needed buttons
in practice.
Also add .catchError to fire-and-forget side button releases during
session teardown to prevent unhandled async errors.
* fix: ensure side button releases bypass permission checks
If permissions change between button down and up (e.g. keyboardPerm
revoked, view-only toggled), sendMouse's early return would suppress
the release, leaving a stuck button on the remote.
Add _sendMouseUnchecked that bypasses permission checks, used for:
- Side button 'up' events (matching a recorded 'down')
- Forced releases during session teardown
Gate all permission checks (isViewOnly, keyboardPerm, isViewCamera)
at the 'down' entry point before recording in _sideButtonDownModels.
* fix: add NULL guards and avoid blocking platform channel handler
- Add NULL checks for FL_VIEW cast and channel creation in
on_subwindow_created (review feedback from fufesou)
- Use fire-and-forget (unawaited) for _sendMouseUnchecked calls
inside the platform channel handler to avoid blocking platform
messages when sessionSendMouse is slow (review feedback from Copilot)
* fix: remove circular import and skip X11 check on Wayland
- Move initSideButtonChannel() call from initEnv() in main.dart to
the InputModel constructor, removing the circular import between
main.dart and input_model.dart
- Skip check_x11_button_map() when DISPLAY is not set to avoid
noisy warnings on pure Wayland environments
* docs: fix typos in documentation and code comments
- Fix 'seperated' -> 'separated' in remote_input.dart
- Fix 'seperators' -> 'separators' in fuse/cs.rs
- Update outdated 'OSX' -> 'macOS' in virtual display README
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>
* impl(cm): implement change_theme and change_language callbacks
These callbacks were previously empty TODO stubs.
Now they properly invoke the Sciter UI handlers to notify
the UI when theme or language changes occur.
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>
---------
Signed-off-by: pallab-js <sonowalpallabjyoti@gmail.com>