CodeVetter · Repo Unpack · Risk map
Genymobile/scrcpy
The riskiest areas are: (1) shell-injection-adjacent code — server params are interpolated into an 'adb shell ... app_process' command line and only mitigated by a denylist VALIDATE_STRING, with an explicit comment that args are not properly escaped on Windows; (2) heavy reflection into private Android APIs (Workarounds, FakeContext, wrappers) which can break per Android release; (3) fragile lifecycle ordering — a large goto-cleanup chain in scrcpy() and static buffers/structs; (4) privileged-environment handling (root uid drop, killing the adb server, writing to /data/local/tmp and /sdcard); and (5) untested protocol duality where the same wire format is hand-maintained in C and Java.
- Server parameter values are appended verbatim to an adb shell command line; validate_string() only rejects a denylist of shell metacharacters (' ;'"*$?&`#\|<>[]{}()!~\r\n') and the comment admits proper escaping does not exist on Windows (sys/win/process.c).
- The command array in execute_server() is a fixed 'const char *cmd[128]' filled by ADD_PARAM macros — adding server options without bound checks could overflow it (only ~60 slots used today).
- SCRCPY_SERVER_PATH env var overrides the pushed server jar; a malicious local env could push an arbitrary jar executed as shell (uid 2000) on the device.
- If run as root, the server silently drops to uid 2000 via Os.setuid — a comment notes copy-paste breaks as root; failure is only a warning (best-effort privilege drop).
- Workarounds uses reflection over private/blocked Android APIs (@SuppressLint PrivateApi/BlockedPrivateApi/SoonBlockedPrivateApi) e.g. ActivityThread internals and fake Context — each new Android version can silently break capture/audio.
- prepareMainLooper() uses reflection on the private static field Looper.sMainLooper — an AOSP-internal implementation detail that could change. (inferred)
- --kill-adb-on-close terminates the entire adb server (sc_adb_kill_server), which has a wide blast radius: it disrupts every other adb session/tool on the machine.
- The control queue silently drops input events beyond 60 pending messages except UHID_CREATE/UHID_DESTROY; under socket backpressure users lose keystrokes with only a verbose-level trace.
- scrcpy() uses a single static 'struct scrcpy' (memset to 42 in debug to catch uninit) and a long goto-based cleanup ladder — adding init steps without updating the teardown ordering risks use-after-free, e.g. screen must be destroyed only after video demuxer join.
- The wire protocol is implemented twice by hand (control_msg.c/h + demuxer.c vs ControlMessage*.java + Streamer.java); nothing enforces parity except matching constants like SC_CONTROL_MSG_MAX_SIZE == MESSAGE_MAX_SIZE == 256k.
- Device reads trust the peer: device_read_info() force-NUL-terminates the 64-byte name ('in case the client sends garbage') but packet sizes drive av_new_packet allocations of up to 4GB-1 declared bytes before any cap check.
- File push writes to device paths: server jar to /data/local/tmp (world-writable staging area) and user files to /sdcard/Download/ by default; SCAN_FILE messages point the device media scanner at pushed paths.
- Version skew is hard-failed: server refuses to run if client version != BuildConfig.VERSION_NAME, so mismatched server jars fail loudly rather than silently misbehaving.