Files
rustdesk/libs
RustDeskandClaude Opus 5.5 08affc69c4 Fix clipboard range overflow (#16329)
* fix(clipboard): unix file server, bound range reads

The Range request fields nPositionLow and cbRequested are UINT32s carried
in i32 fields, but were sign-extended to u64. A negative cbRequested
became a near-u64::MAX length, `offset + length` wrapped past the clamp,
and the server tried to allocate a u64::MAX buffer, aborting the process.
Sign-extending nPositionLow also rejected offsets whose low word is
>= 2 GiB, breaking reads past 2 GiB in large files.

- decode nPositionLow and cbRequested as u32
- clamp with `length > file.size - offset` (offset <= size is checked above)
- allocate the read buffer fallibly and return an error instead of aborting

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(enigo): win, scale absolute mouse moves without overflow

mouse_move_to multiplied the peer-supplied coordinate by 65535 in i32,
which overflows for large values, and divided by the virtual screen size,
which panics if the metrics come back as 0. Scale in i64, clamp to i32,
and skip the move when the virtual screen size is unavailable. Results
for inputs that did not overflow are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(agents): avoid unthrottled high-frequency debug logs

Debug output is written to the log file, so per-packet / per-event log sites
must use trace or hbb_common's throttled_log!.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): unix file server, reject range reads over the frame limit

A response larger than MAX_FRAME_LENGTH (1 GiB) cannot be encoded for
sending, yet the server allocated and read it first, then failed the send.
Reject such requests before allocating, so a peer can no longer make the
server allocate more than 1 GiB per request. Requests that work today are
unaffected.

Drop the extra allocation-failure log; the caller already logs the error.

Tests cover the wire decoding through read_file_contents (negative
cbRequested, nPositionLow past 2 GiB) and the frame-limit rejection. Tests
that use the global CLIP_FILES now take a shared lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): keep file transfers on the file list they started from

A file-contents request names a file only by its index, and the side that
owns the files resolved it against whatever it held at that moment. On
Windows 10+ Explorer's clipboard also offers FileContents, which the owner
re-read from the live clipboard for every chunk, so copying another file
mid-transfer spliced the new file into the one being pasted (#16332). A
second paste, or another connection's paste, replaced the list the same
way, and on macOS/Linux sync_files replaced CLIP_FILES on every host copy.

Each request now names its list in the existing clip_data_id field: an
FNV-1a hash of the file-descriptor PDU, which both sides already hold.
Owners keep the last 4 lists they sent and serve a request from the list
it names; an unknown id fails instead of reading another file. Peers that
send no id read the list last sent to their connection, and a Windows
owner stops reading the live clipboard once it has changed since the list
was built.

Tests cover a host copy and a second paste during a transfer, a list id
sent to another connection, and the hash's reference vectors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): unix file server, cap a single range read at 16 MiB

A peer could still make the server allocate and read up to the 1 GiB frame
limit per request. Cap the data a single range read returns at 16 MiB. The
read is clamped to the rest of the file first, and a larger one is refused
rather than shortened: a short response reads as end of file to IStream
callers on Windows and would silently truncate the copy.

macOS requests 4 MiB per range (BLOCK_SIZE) and FUSE reads are smaller. A
Windows client forwards the application's IStream::Read size, so only an
application reading more than 16 MiB in one call is affected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(enigo): win, warn (throttled) when a mouse move is skipped

A missing virtual screen size is a fault that should show up in a user's
log, but mouse_move_to runs per input event. Log it at warn at most once a
minute via throttled_log! instead of at trace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): review fixes for the file lists kept per transfer

unix owner:
- A retired list closes its files and 8 MiB read buffers; they reopen,
  from offset 0, on the next read.
- The new list is built aside and swapped in only on success, so a failed
  rebuild leaves the current list in place.
- A rebuild with an unchanged id keeps its peers on the new list instead of
  retiring a duplicate, so re-copying a directory no longer pushes out a
  list that a transfer is still reading.

Windows:
- One served-list slot per list, recording every connection it was sent
  to, so pastes of the same list do not use up slots.
- A request for an earlier list is served before OleGetClipboard, so a busy
  clipboard cannot fail it.
- An IStream read over 16 MiB is split into requests the unix file server
  accepts. Reads up to 16 MiB take the unchanged path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): give each copied file list its own id

The list id hashed only the descriptors, so two copies with identical
names, sizes and times (the same file in two folders) shared an id and a
transfer could read the other copy's files. Each owner now writes a random
nonce into the first descriptor's clsid, inside the reserved bytes every
peer skips; no owner sets FD_CLSID. The paste side hashes what it received,
so it needs no change, and older peers ignore the bytes. Re-sending the same
paths with the same descriptors keeps the nonce, and the id.

Windows owner:
- Serve a request from its list before the ownership check when the list is
  no longer current or the host clipboard now holds files from a peer, so
  another controller copying onto the host no longer fails in-flight
  transfers. Only lists sent to that connection are served.
- Evict the list sent least recently instead of in insertion order.

unix owner: take everything from the crate in one use block.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard,enigo): bound served-list reads, clamp absolute moves

wf_cliprdr: the served-list branch runs before the ownership check, yet it
allocated the peer's cbRequested (up to 4 GiB) before looking the list up.
Accept only FILECONTENTS_SIZE or a range of at most 16 MiB there, which is
all that clients sending an id ever request, and fail anything else before
allocating.

enigo: clamp the scaled absolute coordinate to 0..=65535, the range
MOUSEEVENTF_ABSOLUTE takes, instead of to the i32 range. Coordinates on the
virtual desktop give the same result as before; off-desktop ones now land on
its edge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): keep a file list while it is being read, throttle read errors

A unix paste side fetches every new file list as soon as the host copies,
so each copy retires the previous list, and four copies during one long
transfer evicted the list it was still reading. A kept list that serves a
read now becomes the most recent (unix: back of the retired queue, Windows:
last_served), so the next eviction takes a list nobody is reading.

A failed file-contents read logged an error per request, and peers can now
trigger new ones (an unknown list id, an over-limit range). Log it through
throttled_log! instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): unix, keep retired lists in the order they were sent

A peer that sends no list id is served the list last sent to its
connection, found by searching the retired queue from the back. That only
holds while the queue stays in retirement order, which is the order lists
were sent. The previous commit moved a list to the back on every read, so
another connection reading an older list could make a peer's next chunk
come from that list: a transfer of B continued with A's bytes.

Keep the queue in retirement order and track recency in a separate
last_used stamp, set on retirement and on each read. A full queue evicts the
list with the lowest stamp, so a list still being read is kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): unix, keep files being read open when their list is retired

Retiring a list closed every file handle, so the next read of a file part-way
through reopened it by path. If that path had been replaced meanwhile (an
editor saving over it), the rest of the transfer came from the replacement:
AAAABBBB arrived as AAAADDDD. Close only files that are not being read, such
as the preloaded next file; a file part-way through keeps its handle and
offset, and so its identity.

Tests read two files of one list alternately, retire the list, replace both
paths by rename, and check each file continues with its original bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): cap Windows ranges, expire idle kept lists, check file identity

Windows owner: refuse every FILECONTENTS_RANGE over 16 MiB before
allocating. Only the served-list branch was capped; requests without an id
still allocated whatever the peer asked for, up to 4 GiB.

Both owners: a kept (retired / non-current) file list that nobody has read
for 5 minutes is dropped, with its open files; its transfer is over or its
peer gone. The current list never expires, as before. On Windows, reads of
the current list through the existing path now also refresh its kept copy,
so a long transfer does not lose its list once a newer one is sent.

Windows owner: old-list reads compare the file's size and last-write time
with the descriptor that was sent, on the handle they read from, and fail
on a mismatch instead of reading whatever file now has that path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(clipboard): unix, check list expiry without backdating an Instant

an_idle_retired_list_expires backdated a timestamp with
Instant::now().checked_sub(TTL + 1s), which is None while the monotonic
clock is under about five minutes, as on a freshly booted CI machine; the
list then never expired and the test failed. expire_retired now takes the
current time, and the test passes a later one instead. Callers pass
Instant::now(), so behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(clipboard): drop read-time refresh, idle expiry and rebuild reuse; tighten checks

Remove the read-time recency and the 5-minute idle expiry on both owners,
and the unix reuse of an unchanged rebuild (has_same_files). They covered
corner cases, added state, and each brought regressions of its own. Kept
lists are again evicted oldest first (unix) or least recently sent
(Windows), 4 at most; a transfer that outlives 4 newer lists fails cleanly.

Windows: serve kept lists after the existing ownership check again, so a
request carrying a list id no longer bypasses it while the host clipboard
holds files from another session. Refuse request types other than size or
range before allocating.

unix: a retired list's file must still match the size and modified time it
was listed with, checked on the handle the read uses; a file replaced at
its path before its read now fails instead of being served.

Tests: drop the four tests of the removed behavior, add
a_retired_list_refuses_a_file_replaced_before_its_read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(agents): handle corner cases raised in review with small fixes or docs

A refactor added to cover a corner case rarely converges: new counters,
timestamps and expiry rules interact with state existing code relies on,
and the next review round finds the problems they introduced. Fix a corner
case when the fix is a few lines with no new state; otherwise document it
as a known limit, and ask before anything larger.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 11:25:12 +08:00
..
2026-09-23 17:30:22 +08:00
2026-09-23 18:40:49 +08:00