Update MeshAgent from Fork - #33
Open
ivan-flamingo wants to merge 68 commits into
Open
Conversation
* Removed trying to load Windows system DLLs from user folders Fixes Ylianst/MeshCentral#7606
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
Updated CodeQL GitHub Actions to use the latest versions.
Uncomment language selection for CodeQL analysis.
…dded monitor-border.js to the polyfills (#331) * Added option to limit the width of the desktop notification bar. * Added monitor-border to the embedded modules in the polyfills. Added auto-start too.
* Fix strcat_s implementation Old implementation didn't work, it would crash when it tried to dereference and set a null pointer (*x = i). This version works by setting the default size to destinationSize, so it will throw in the later check if no \0 is found. This function is otherwise unused in the code. * Added function to load certs from the system Windows doesn't respect the openssl standard, so that needed to be handled manuall. Since this project uses a statically linked openssl, the default locations may be inaccurate. If that directory doesn't exist, it tries to get it from the system openssl via a popen. If that also fails, it will not be able to load system certs. * Added system cert loading wherever an SSL context is established * Modified OnHttpsConnection callback so the binder can pass general data to the callback. This was necessary to get a handle on the agent during the connection callback. * Added check for validateWebCert config option This will cause the connection to fail if the value is set and the agent attempts to connect to a server with an invalid web certificate * Fixed some handling of rejectUnauthorized in duktape net handler Before it would override any user defined checks; now it will foil to connect if the system certs don't match, but will still run user checks if it passes those. * Added validatewebcert arg to meshcmd.js This will cause rejectUnauthorized to be true if you pass it in * Updated default recovery core to check for the "validateWebCert" option and use rejectUnauthorized if it is set.
… ARM systems (#224) * Force alignment of allocated memory. Without this change, I have noticed crashes on ARM based devices which have stricter alignment requirements than x86. Ideally, this code should be rewritten to pad the sizes of all "structs" and to consider all of the different architecture-specific alignment requirements. But in practice, I suspect that rounding up allocation sizes to a multiple of pointer sizes is likely good enough. This is a pragmatic one-line change that shouldn't break existing code and should prevent any of the crashes that I have observed. * Added a few more memory alignments when using both primary and extra memory in a back-to-back memory allocation.
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
System proxy detection executes the proxy-helper JavaScript path, which can create short-lived EventEmitter and ChildProcess object graphs during reconnect attempts. Without an explicit GC pass those objects can remain retained until a later heap cycle, causing repeated reconnects to drive up peak heap usage. Run a Duktape GC pass immediately after popping the proxy-helper result so the temporary proxy-detection objects are collected promptly.
…ut (#342) When MeshServer_ConnectEx_NetworkError fires (20s connection timeout), it frees j (==agent->controlChannelRequest) but does not set the pointer to NULL. ILibWebClient_CancelRequest, called immediately after, executes synchronously on the chain thread (ILibChain_RunOnMicrostackThread macro calls the handler inline when already on the chain thread). This triggers MeshServer_OnResponse(ReceiveStatus_Complete) which sees a non-NULL controlChannelRequest and calls ILibMemory_Free on the already-freed pointer, causing a double-free and heap corruption on every retry cycle. Fix: set agent->controlChannelRequest = NULL right after ILibMemory_Free(j) so MeshServer_OnResponse safely skips the cleanup block. Relates to: #110, #151, #281 Relates to: Ylianst/MeshCentral#7407 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…re (#344) On macOS Tahoe (26.x), the dashboard's Desktop tab shows a black frame on Apple-Silicon Macs because the user-context -kvm0 helper inherits the daemon's system-side audit session (asid != console user's gui session asid). Per-user XPC services like com.apple.replayd are registered in gui/<console-uid> and unreachable from a different audit session, so SLSHWCaptureDesktopProxying / ReplayKit returns empty frames after a 5-second timeout. This patch joins the console user's GUI audit session at TWO points: 1. Pre-exec, inside SpawnProcessEx4's fork-child while still root: does the actual auditon-based discovery and audit_session_join. On Tahoe, only root can call audit_session_port() for arbitrary asids or enumerate via auditon(A_GETPINFO_ADDR) across audit sessions, so discovery has to happen here. 2. Post-exec, in the helper's main() entry for argv[1] == "-kvm0": the kernel resets the asid on exec, so the pre-exec join doesn't persist on its own. Post-exec re-joins the same session; the pre-exec join leaves enough kernel state that auditon visibility for the user's gui session works from the dropped-privilege uid. Discovery picks the LOWEST visible asid belonging to the target uid (typically 100002, the user's primary gui session, vs higher asids for transient/per-app sessions where replayd doesn't register). A fallback brute-force range probe handles the case where Tahoe's cross-session auditon visibility filter blocks discovery entirely. Also includes a small zlib build fix in meshcore/zlib/zutil.h: skip the "fdopen() not available" macro on __APPLE__ because newer Xcode/ Clang treats the resulting macro expansion as a compile error against the system stdio.h prototype. macOS has fdopen, so the macro shouldn't fire on Apple platforms anyway. No behavior change on Linux/Windows. No new dependencies (audit APIs ship with the macOS SDK). Tested on macOS Tahoe 26.x, Apple Silicon, MeshCentral 1.x server. Before patch: 100% black canvas in dashboard Desktop tab. After patch: full desktop video streaming at native resolution. Co-authored-by: austin <me@austinrenfroe.com>
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
…workaround) (#349) * macOS Tahoe: post mouseMoved CGEvent instead of CGWarpMouseCursorPosition On Tahoe, CGWarpMouseCursorPosition fires hot-corner watchers when the warp destination sits in a corner zone — the OS treats the warp as a cursor 'presence' at the destination rather than silent teleportation. On every Desktop-tab connect from the MeshCentral browser UI, the initial mouse coordinate sent by the browser commonly lands in the top-right of the video pane, the warp puts the OS cursor at the top-right of the screen, and Tahoe fires Notification Center (surfacing Calendar + Weather widgets). Top-left maps to Mission Control / app-spread the same way. Posting a kCGEventMouseMoved CGEvent goes through the normal HID-event path. Hot-corner watchers fire on cursor *dwell*, not on synthetic movement events, so this stops triggering Notification Center / Mission Control on connect. Subsequent browser-side cursor movement generates normal CGEvents that move the OS cursor away from any corner the cursor briefly visits. This also makes mesh's mouse-move behavior more conformant with what macOS apps expect from input frameworks — CGWarpMouseCursorPosition is documented as 'does not generate mouse events,' which means apps that listen for mouseMoved (screensaver wake, sticky-corner activation, HID-tracking apps) miss out on cursor activity that's actually happening from the user's perspective. * macOS Tahoe: add -kvmagent mode + LaunchAgent plist (Phase 1) The user-context LaunchAgent runs natively in gui/<console-uid> with the user's audit session id (typically asid=100002), where com.apple.replayd registers. This eliminates the cross-session XPC bootstrap-lookup failure that breaks ScreenCaptureKit screen capture when the kvm helper is spawned from the system-side LaunchDaemon (asid=100001). Architecture matches Splashtop Streamer's: a separate per-user LaunchAgent with LimitLoadToSessionType = ['Aqua', 'LoginWindow'], loaded by launchd into the user's domain. The daemon connects to the agent over a Unix domain socket; same TLV protocol that flowed over the inherited stdio pipes in the -kvm0 / -kvm1 case. Phase 1 deliverables: - mac_kvm.c: convert KVM_Listener_Path from #define to a writable variable + setter, so the listener path can be parameterized for the per-user agent. Defaults preserve legacy -kvm1 behavior. - meshconsole/main.c: new -kvmagent mode. Optional argv[2] sets the socket path; default is /tmp/meshagent-kvm-<uid>.sock so a non- root user can bind it. - macos/com.meshagent.kvm.plist: LaunchAgent definition. RunAtLoad true, KeepAlive on non-clean-exit, ThrottleInterval 30s. Phase 2 (next): daemon's kvm_relay_setup() in mac_kvm.c connects to this socket instead of fork-exec'ing a -kvm0 helper. Phase 3: drop the audit_session_join workaround entirely once the daemon-side connect path is live and verified. * macOS Tahoe: daemon connects to user-LaunchAgent socket (Phase 2) Phase 2 of the launchagent-split refactor. Adds the daemon-side socket-connect path that pairs with the -kvmagent mode added in Phase 1. When MeshCentral initiates a Desktop session, kvm_relay_setup() now first tries to connect to /tmp/meshagent-kvm-<uid>.sock — the listener registered by the user-context LaunchAgent. If the agent is up, the daemon hands the KVM session off to it via the existing TLV protocol (now flowing over a Unix domain socket instead of inherited stdio pipes from a fork-exec'd helper). If the agent isn't reachable, falls through to the legacy fork-exec -kvm0 helper, preserving the existing behavior for fixtures that don't yet have the LaunchAgent installed. The agent's audit session is gui/<uid> natively (via launchd's LimitLoadToSessionType=Aqua loading), so com.apple.replayd is reachable from there and ScreenCaptureKit screen capture works without the audit_session_join workaround the daemon-spawn path needs. Cleanly supersedes PR #344's approach. Implementation: - New static state (g_kvmSocketPipe, g_kvmSocketFD, write handler + reserved pointer) tracking the socket-mode session. - kvm_relay_setup tries connect(); on success wraps the FD in ILibProcessPipe_Pipe_CreateFromExisting() so the existing pipe abstractions for write / pause / resume / free continue to work. - New kvm_relay_socket_ReadHandler with the ILibProcessPipe_Pipe_ReadHandler signature; reuses the same TLV parser as kvm_relay_StdOutHandler. - New kvm_relay_socket_BrokenHandler clears the static state on socket disconnect so subsequent kvm_relay_setup calls reconnect. - kvm_relay_feeddata branches: ILibProcessPipe_Pipe_Write to the socket pipe in socket mode; ILibProcessPipe_Process_WriteStdIn to the legacy child process in fork-exec mode. * macOS Tahoe: process key/mouse events locally when in socket mode Upstream's MNG_KVM_KEY and MNG_KVM_MOUSE cases in kvm_mainloopinput bail out when KVM_AGENT_FD != -1 — i.e., when the daemon is in the unused -kvm1 socket-mode (or now our -kvmagent mode). Presumably intended for an upstream design where the agent forwarded input messages elsewhere instead of dispatching them locally. That's wrong for the user-LaunchAgent architecture: the agent IS the process that should call KeyAction / MouseAction, since it's the only one in the right audit session and with the TCC permissions to post CGEvents into the user's session. The system-level daemon proxies bytes only. Symptom before fix: with the LaunchAgent up and the daemon connecting to it via socket, video streamed fine but every keystroke and mouse motion was silently dropped at the case-break. After fix: the agent handles input locally, video + keyboard + mouse all flow end-to-end through MeshCentral's Desktop tab. Verified empirically 2026-05-06. * macOS Tahoe: integrate -kvmagent into -fullinstall + harden socket-mode fallback Two follow-ups from the launchagent-split refactor's first working deployment: 1. agent-installer.js: -fullinstall now writes the LaunchAgent plist with parameters: ['-kvmagent'] (was ['-kvm1'] - never wired up on the daemon side) and sessionTypes: ['LoginWindow', 'Aqua'] (was ['LoginWindow'] only - meant the agent never loaded for logged-in user sessions). After installing the plist, immediately bootstraps it into gui/<console-uid> so the agent is available without requiring a logout/login cycle. 2. mac_kvm.c kvm_relay_setup hardening: only fall through to the legacy fork-exec helper if the LaunchAgent socket file is genuinely absent from disk. If the socket exists but connect() transiently fails (e.g. agent re-accepting between sessions), retry up to 5 times with exponential backoff (50ms..800ms, ~1.55s total). Returns NULL if all retries fail rather than firing the broken fork-exec path that produces black frames / dead input under Tahoe's audit-session isolation. Adds <sys/stat.h> for the stat() check. * macOS Tahoe: regenerate embedded modules with updated agent-installer.js Run code-utils expand/shrink to refresh the auto-generated block in microscript/ILibDuktape_Polyfills.c so the agent-installer module embedded in the binary reflects the LaunchAgent + -kvmagent changes made in modules/agent-installer.js earlier in this PR (commits b658692, 38d0120, 4a8a589, d77773f). Process used (per si458's note): ./meshagent_x86-64 -export \ --expandedPath=modules_expanded \ --filePath=microscript/ILibDuktape_Polyfills.c cp modules/agent-installer.js modules_expanded/agent-installer.js ./meshagent_x86-64 -exec \ 'require("code-utils").shrink({ expandedPath:"modules_expanded", filePath:"microscript/ILibDuktape_Polyfills.c", modulesPath:"modules"}); process.exit();' Diff is bounded to the BEGIN/END AUTO-GENERATED BODY markers. Module set is unchanged (1 net add: wget is now embedded as a real module rather than the prior lazy require() property; the corresponding process.arch lazy-getter remains in place outside the auto-gen region). --------- Co-authored-by: austin <me@austinrenfroe.com> Co-authored-by: Austin Renfroe <austin@earenfroe.com>
* Add build files to .gitignore * Remove build file
Once the log reached maxLogSize it stopped silently and didn't resume until the file was deleted (#357). Keep that as the default but write a single marker line when the cap is hit, and add two opt-in alternatives: logRotate=N rotate to <log>.1 .. <log>.N at maxLogSize, then start fresh logTruncate=1 clear the file and keep logging instead of stopping logRotate takes precedence if both are set.
* fix typo: integragedJavaScriptLen > integratedJavaScriptLen * Fix -exec arg parameter length Now uses the real size of the provided script. ILibScratchPad is a 4096 char buffer and ILibStringCopy with a length arg of 0 also uses iLibScratchPad as a default/max size. If a larger script was provided, it got silently truncated and execution failed. * Fix -b64exec parameter length Use the real length of the provided script. ILibScratchPad2 is a 64k buffer, large, but with base64 text it could still be a reachable limit.
Fix Base64 pad bits for one-byte final blocks ILibencodeblock read in[1] even when len was 1. This caused an out-of-bounds read and allowed four adjacent-memory bits to populate the unused Base64 pad bits. WebSocket client nonces are 16 bytes, so every Sec-WebSocket-Key generation exercised this path. Strict RFC 4648 decoders reject the resulting noncanonical encoding. Zero the unused bits when len is 1 and add RFC 4648/RFC 6455 regression coverage. Impact assessment: This change does not modify the decoded nonce bytes, protocol version, agent configuration, or server behavior. It only ensures that unused Base64 pad bits are zero and prevents the one-byte out-of-bounds read. Co-authored-by: Anthony Cochenour <acochenour@hopliteindustries.com>
* close `HCERTSTORE` and `HCRYPTMSG` handles in `win-authenticode-opus` * Add missing call to `ILibDestructParserResults` * Remove redundant `ILibMemory_Free`, already cleared in `ILibLifeTime_Remove` via `DestroyPtr` * Add another explicit call to Duktape Garbage Collector * Cache Win32 authenticode check across reconnect cycles * Clean-up the authenticode cache code
This workflow defines jobs for building the MeshAgent for both arm64 and x86_64 architectures on macOS. It includes steps for checking out the code, building the agent, and uploading the artifacts.
- Introduced `.github/workflows/freebsd-build.yml` to enable FreeBSD binary builds inside a VM on a Linux runner, targeting x86_64 architecture. - Added `.github/PULL_REQUEST_TEMPLATE.md` for standardized pull request submissions with testing and documentation guidelines.
…` to resolve missing header issue. (#385)
* Add support for Wayland on Linux The Wayland support comes from changing the way input and output works: 1. Screen output is captured via DRM (Direct Rendering Manager) 2. Keyboard/Mouse input is injected via libevdev Both of these methods are lower level than the previous X11 code, so they're actually capable of becoming full replacements for the X11 mechanisms, should we ever want to do that. I've tried to make the changes minimally invasive to the existing code, instead of a big refactor into X11 vs DRM code paths. Known issues: 1. Logging out back to the greeter breaks the screen scraping. This is fixable, just requires work to figure some things out. 2. Cursor state is not reflected in the screen output To get the greeter working will require some more surgery. I haven't figured out how all of that session stuff works yet, but it is possible. It's largely just an issue of the system getting confused between Wayland/X11 states. One quite simple solution could be that once we detect Wayland, we remain in that state for the duration of the process life. I'm not sure if people care about dynamic switching between X11 and Wayland during a single boot - and I suspect not, so I think this would be a simple and robust solution to get the greeter working. If cursor state is necessary, then I can look into that. It just hasn't felt like a priority. I don't consider this work 100% finished. We still need to get the greeter working, and more testing/feedback. But if possible, I'd like to get this on a public branch so that I can start getting feedback. This work is sponsored by touchsource.com * Add new makefile linux package list for EGL/DRM * Fix signature of ValidateMeshServer This fixes a warning on modern compilers * Add a Wayland detection mechanism when there's no Xorg/X11 on the system Previously, we only worked on eg Ubuntu 24.04, where they still shipped Xorg/X11. Without this change the agent does not advertise desktop capability, and the "Desktop" tab in MeshCentral is unavailable. * Make wayland sticky This prevents us from switching back to X11 mode if we connect at the greeter screen. We may still be able to simplify the wayland detection to just the seat0 read. * Delete hacked-in polyfills.c code It was already in monitor-info.js * Add more logging and error checking to DRM/EGL code paths Aiming to find the reason why we're failing on i915 * Fix DRM capture breakage on screen blank, fix DRM handle leak * The screen capture now resumes automatically after a blank screen * Fixed leaking of framebuffer handle * Fixed spurious noisy logs on nvidia capture due to changing handles * Add rudimentary support for rotated wayland displays Unfortunately I haven't yet found a reliable universal way to detect display rotation on wayland, so I'm adding an env var override, eg For "portrait right": MESH_KVM_ROTATION=270 By setting MESH_KVM_ROTATION to anything besides zero, we force the treatment of the display as rotated by either 90,180,270 degrees. I split it into it's own .c file, because I forsee this getting a bit messy, having to support various compositors. * Improve DRM Wayland KVM multi-monitor layout handling Add multi-output DRM capture support and compose active scanout buffers into a single logical desktop frame. When running under KWin, read the session's logical output geometry and apply it to the DRM outputs so capture and input coordinates follow the desktop monitor arrangement, including scaled displays. Also make verbose DRM/EGL diagnostics opt-in through MESH_KVM_DRM_DEBUG to avoid repeated framebuffer logging during normal agent operation. * Require Wayland client support for Linux KVM builds Make Wayland client support mandatory for Linux KVM builds so release and CI builds cannot silently omit the generic xdg-output monitor layout path. The DRM backend now includes wayland-client directly, and the makefile fails early when the wayland-client development package is unavailable. This keeps Wayland monitor positioning and scaling behavior consistent between build servers and deployed agents. * Add DRM monitor selection support Populate the existing monitor list from DRM/Wayland logical outputs and advertise the normal multi-monitor display list in DRM mode. Allow MNG_KVM_SET_DISPLAY to select all monitors or an individual monitor, crop DRM capture to the selected output, and keep evdev absolute input mapped against the full virtual desktop so pointer control remains correct across non-origin displays. * Implement dynamic `dlopen` loading for libdrm, libEGL, and libGLESv2 Enable runtime loading of required libraries (libdrm, libEGL, and libGLESv2) using `dlopen` to allow the agent to function without a pre-installed GL/DRM stack. Refactor the makefile to remove hard library linking while retaining headers for build compatibility. Additionally, update Wayland integration to dynamically load libwayland-client and provide fallbacks for missing libraries. * Add display wake and broken-pipe handling improvements for DRM and KVM Introduce logic to nudge sleeping displays (DPMS) awake during DRM desktop layout calculations and retry within a timeout. Enhance KVM pipe cleanup by canceling timers and broken-pipe handlers to prevent errors from delayed memory access. Define `KVM_DRM_DISPLAY_WAKE_TIMEOUT_MS` and implement `kvm_events_evdev_wake` for pointer-based wake functionality. * Capture all GPUs in DRM KVM, not just one card Open every DRM card that has an active display and merge their outputs into one desktop, sorted by position. Each card gets its own EGL context pinned to its DRM node so a second GPU's frames aren't imported through the wrong driver and come out black. Also stops two cards with the same connector name (DP-1, etc.) from landing on the same monitor. * Update evdev pointer scaling logic to prevent double offset in single-monitor capture mode * Add Wayland clipboard support and improve evdev CapsLock handling Clipboard get/set only had the X11/xclip path, which cant reach the clipboard under Wayland. Added a wl-clipboard (wl-copy/wl-paste) backend that's used when a Wayland session is detected. Needs wl-clipboard installed on the target. The evdev key path dropped the Shift modifier on typed text, so shifted symbols came through wrong ($ became 4, _ became -, etc.), and it ignored Caps Lock so the case followed the remote Caps Lock state. Sync after each transition so Shift is held while the key is sent, and read the Caps Lock LED to keep the requested case for letters. * Update compressed clipboard module logic and source commit metadata * Fix DRM KVM black screening from lost GPU device access after a privilege drop The DRM capture child opens the card nodes as root, then drops to the logged-in user keeping only CAP_SYS_ADMIN. That also drops CAP_DAC_OVERRIDE, so from there access to /dev/dri/* comes down to plain group permissions. libEGL opens the render/card nodes itself during eglInitialize, which happens after the drop, so unless the session user was already in the render/video groups it failed with EACCES and the remote desktop came through black. The old X11 path never hit this because it goes through the X server socket and never touches the DRI nodes. Add the video and render groups to the session user's supplementary set during the drop, before the setuid, so GPU conversion works regardless of which groups the user is in or how mesa is packaged. Groups the distro doesn't define are skipped. Also mirror kvm_send_error() to stderr and move the evdev input-failure diagnostics off stdout. These only reached the MeshCentral viewer, or stdout which the systemd unit nulls, so permission failures like this were invisible in the agent's own logs. * Install libwayland-dev in the Linux build workflow The KVM builds need wayland-client (headers to compile linux_kvm_drm.c, and the makefile now hard-requires it), but the install step never pulled in libwayland-dev, so every KVM target failed at the wayland-client check before compiling anything. It's dlopen'd not linked, so the 64-bit dev package is enough for the -m32 build too. * Install libdrm/EGL/GLES dev headers in the Linux build workflow The DRM KVM path includes <EGL/egl.h>, <GLES2/gl.h> and the libdrm headers directly (they're dlopen'd at runtime but still needed to compile), so the KVM builds failed on EGL/egl.h now that they get past the wayland-client check. Add libdrm-dev, libegl-dev and libgles-dev to both jobs. Headers only, nothing links them, so the 64-bit packages cover the -m32 build too. * Fix Wayland "Type" shift-drop on GNOME + re-embed stale clipboard modules The evdev unicode/Type path fired shift→key→release with no gap, and GNOME (via IBus) drops the modifier when events arrive that fast, so shifted chars lost their shift ($→4, _→-, |→\, caps→lowercase). Hold the key ~20ms with Shift held and pace characters out (same as ydotool on Wayland); tunable via MESH_KVM_KEY_DELAY_MS. KWin was unaffected. Also re-embedded the clipboard + monitor-info compressed modules, which were stale and missing the Wayland (wl-clipboard) code entirely. * Fix Wayland KVM multi-monitor layout (GNOME) and multi-plane (CCS) scanout - Multi-monitor 0,0 on GNOME: mutter names HDMI type-A "HDMI-<id>" vs our "HDMI-A-<id>", so the exact-strcmp connector match failed and the all-or-nothing xdg-output layout fell back to raw DRM 0,0. Add kvm_drm_connector_name_equal(). - Black screen on Intel CCS: two-plane (data+metadata) scanout were rejected; import all planes through EGL so the GPU decompresses during the blit. * Refactor Wayland session detection and Wayland greeter fallback - Updated `user-sessions.js` to handle case-insensitive matching for 'Display Manager', recognizing additional greeters (e.g., `_ldm`). - Enhanced `monitor-info.js` to detect Wayland sessions by introducing `kvmcheck_wayland_user` and implementing a fallback for greeter sessions (e.g., GDM/LightDM) to ensure detection before user login. - Re-embedded the updated compressed `monitor-info` and `user-sessions` modules. * Introduce graceful KVM session recovery during logout/user-switch - Added runtime-dir checks and grace periods to distinguish between transient display sleep and actual session teardown in the DRM path. - Implemented auto-recovery logic in capture child to handle unexpected exits, automatically re-derive active sessions, and re-fork capture processes, with burst-rate limiting to prevent fork storms. - Improved robustness in session user environment handling by avoiding restarts when X server properties are unavailable in Wayland environments. * Add rotation handling for Wayland DRM outputs and debug frame dumping - Implemented `kvm_drm_rotation` to account for rotation transforms for Wayland outputs and logical desktop alignment. - Added `kvm_drm_debug_dump_frame` to debug desktop orientation/layout issues by exporting PPM captures periodically. - Updated function signatures and Wayland transform handling to support output rotation, ensuring proper compositor synchronization. - Adjusted Makefile indentation for consistency. * Implement deadlock-safe writes to slave->master pipe in DRM mode - Introduced `kvm_drm_slave_write` for non-blocking, deadlock-safe writes, preventing session hangs under backpressure. - Replaced all direct `write` calls in DRM paths with the new draining writer to ensure stream consistency. - Added rotation handling for DRM planes via property queries, with fallback strategies for unsupported configurations. - Improved input message handling with buffered, real-time parsing to prevent interleaving issues. - Updated DRM initialization flow to enable universal planes and non-blocking pipes, supporting advanced configurations like plane rotation and multi-plane hardware scanouts. * Add XKB keymap support for Wayland sessions in KVM - Implemented `linux_kvm_xkb.c` to resolve Unicode codepoints to Wayland XKB keymap keystrokes, enabling input in non-US layouts and scripts. - Integrated XKB with the evdev input pipeline for seamless handling of Shift, AltGr, and CapsLock modifiers. - Updated `linux_events_evdev.c` with fallback to ASCII mapping when XKB is unavailable. * Refactor Wayland KVM implementation for improved compatibility and reliability - Replaced `wl_proxy_marshal_flags` with array marshallers for compatibility with libwayland < 1.20 on older systems (e.g., Debian 11 / Ubuntu 20.04). - Added deadline-bounded alternatives to `wl_display_roundtrip` to prevent session input/output stalls in hung compositors. - Enhanced DRM resource cleanup with `kvm_drm_close_frame_handles` to prevent GEM handle leaks. - Improved session user privilege drops with full cleanup of supplementary groups for security. - Introduced logging improvements for debugging Wayland and DRM behaviors. - Adjusted Makefile to validate required development headers, ensuring smoother build workflows. - Updated evdev F-key mappings and error output for consistent and readable diagnostic messages. * Enhance Wayland KVM fallback and input handling - Added fallback for compositors without `zxdg_output_manager_v1`, leveraging `wl_output` geometry and mode events as a minimal implementation for Greeter environments. - Improved lock state synchronization for evdev keyboards by adding `kvm_events_evdev_lock_state` and `kvm_events_evdev_wait_lock_state`, ensuring accurate CapsLock, NumLock, and ScrollLock state reporting. - Integrated Wayland active session UID derivation for display-manager greeters using `kvm_wayland_active_console_uid`. - Updated XKB keymap handling with group-aware lookup for precise input handling in multi-layout scenarios. - Introduced bounded timeouts for `wl-paste` and `wl-copy` to handle unresponsive compositor pipelines gracefully. - Optimized clipboard dispatch with lifetime caching of Wayland session state for reduced probe costs. * Improve session UID handling and environment inheritance in DRM path - Set `HOME` to the session user's directory to fix cache warnings in Mesa. - Trust `logind` for active session UID derivation, avoiding dead session binding. - Implement session handover detection with console UID mismatch checks. - Exit and re-fork on sustained UID mismatches during user switches or logouts. * Improve Wayland socket detection and error logging in DRM/XKB paths - Added `kvm_drm_wayland_socket_alive` to validate Wayland sockets with `connect()` checks, avoiding stale socket usage. - Improved error logging for `wl_display_connect` failures with additional context (`errno`, `XDG_RUNTIME_DIR`, `WAYLAND_DISPLAY`, etc.). - Updated environment preparation to scan for active Wayland sockets and prioritize live connections dynamically. - Enhanced fallback logic for `WAYLAND_DISPLAY` environment variable handling to improve robustness in session initialization. * Override inherited session environment variables for sudo scenarios - Updated `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` logic to prevent reuse of parent session values under sudo, addressing permission issues with root's runtime directory. - Added runtime directory existence checks to derive accurate session paths while retaining fallback for unsupported setups. --------- Co-authored-by: Ben Harper <rogojin@gmail.com>
/tmp/master is unguarded. It runs every relay start as root Guard it like /tmp/slave on line 1653
fs defaulted to "r" and "w", which on windows is a text stream. Node has no text mode, and on posix text and binary streams are identical, so a mode string without b or t should open binary. A text stream expands every written \n to \r\n, and a read stops at the first 0x1A byte, which it reads as an end of file. ILibDuktape_fs_openSyncEx/readFileSync now appends b when the called without b or t. createReadStream/createWriteStream also default to "rb"/"wb", even though openSyncEx already forces it.
The sigtable array index was used directly as the OS signal number, but sparc/mips linux don't share the generic numbering, so sigaction() hooked the wrong signal (e.g. SIGCHLD silently hooked SIGSTOP on sparc64). Replaced the string array with a signal map of name/number pairs generated from the target's real <signal.h> macros at compile time, so it's always correct for the compiled target. The Windows table is just the generic Linux numbers used to fill process.SIGTABLE, since Windows doesn't do POSIX signals.
) ProcessCommand and BinaryCommand now print the MeshCommands_Binary name next to the number. The enum and the name switch get generated from one #define list, so future changes to the command list stay in sync. Also cleaned up the debug output: - trailing newlines removed from .log entries, missing ones added to printf. - All controlChannelDebug tracemessages now go to both console and log. - No duplicate output lines - Fixed one unreachable log line.
…394) Ensure remote desktop capture works at login screens by running the capture child as root when `consoleUid()` matches the display manager's uid, addressing issues with root-owned X server cookies. Applied in both initial capture and re-fork paths for logout/user-switch scenarios.
…#396) Under clang/compiler-rt toolchain this failed to link on ppc64le with undefined symbol __gcc_qsub / __gcc_qmul. ILibDuktape_WebSocket_bytesSent_ratio/bytesReceived_ratio calculated the ratio with long doubles. Long double is not a strict defined type per platform, making it less portable. The proper 'long double' precision would be __float128. But it's just a ratio calculation which doesn't need this high precision at all, and in the end it returns the floored value as a JS number. This is the only place in the source where long doubles were used.
…393) Done because MSVC x86 (x64 has no issue) link-time code generation (v143 and v145, v142 verified unaffected) drops the conditional part (extraLen > 0 ? header + roundup : 0) of the ILibMemory_Init_Size macro when the sizes are not constants. So the first block with a non-zero extraLen got 44 bytes instead of 88, and every x86 Release build died at startup with 0xC0000374 (STATUS_HEAP_CORRUPTION). The same LTCG build also dropped two of the four ILibMemory_Size_Validate checks. Both are now static inline functions for runtime sizes. The macros stay for the constant-size callers. Other solution would be disabling LTCG, but this costs nothing performance-wise and makes it independent of the LTCG option and prevents future issues.
Replace ((type*)ptr)[n] pointer casts with memcpy-based ILibUnaligned_Read/Write16/32/64 helpers to avoid SIGBUS on MIPS/ARMv5/RISC-V/SPARC.
* Add optional mTLS client certificate for the server connection * Document the ClientCertPem option * Use the mTLS client certificate for relay tunnels as well
Fixes 'ld-elf.so.1: Shared object "libutil.so.9" not found, required by "meshagent"' libutil's SONAME is not stable across FreeBSD releases (.so.9 on 14.x vs .so.10 on 15.x), so a binary linked against one fails to load on the other. Meshagent needed libutil for only one symbol: forkpty() - Reimplement forkpty() locally from libc POSIX primitives instead of linking -lutil.
#388) Co-authored-by: denis <denis@qilk.net>
ARCHID 33 set no CC, so it fell through to the default "CC = gcc" and the agent was compiled against glibc. Every other musl target already names its compiler explicitly; this one was the exception. With CC set the same way the OpenSSL archive is built, the agent comes out native musl: -idirafter rather than -I is deliberate and required: musl-gcc drives the host gcc with -nostdinc, so kernel headers are not on the include path and <linux/limits.h> is not found. Appending the system directories after musl's lets musl win for everything it provides while linux/* and asm/* come from the host; -I would shadow musl's headers with glibc's.
Added musl-tools to the list of installed packages for the build process.
…ew feedback + working macOS CI (#380) * SafeDuktape_CallMethod: guard pcall against stale stream heapptrs When a heap-stashed object (e.g. state->writeStream) has been closed, GC'd, or replaced between the moment its heapptr was captured and the moment we call one of its methods, the old code went: duk_get_prop_string(ctx, -1, "write"); // returns undefined ... duk_pcall_method(ctx, N); // TypeError: ... of undefined Because the failure originates from a native callback, the error cannot be reached by a JS try/catch. Duktape then promotes it to a fatal, and ILibDuktape_ScriptContainer_Engine_fatal exits the process with ILIBCRITICALEXITMSG(254, ...). This is the dominant crash mode on macOS 26 (Tahoe) when the KVM LaunchAgent (`meshagent-launchagent`) restarts faster than the daemon can clean up the relay socket (see issue #359). Add ILibDuktape_SafePcallMethod: a thin wrapper that: - Verifies the callable is actually a function (the Duktape pcall_method layout puts the callable at -(nargs + 2) and `this` at -(nargs + 1)). - If not, drops the (callable, this, args) from the stack and routes to Process_UncaughtExceptionEx so the engine keeps running. - Otherwise behaves exactly like duk_pcall_method, but also routes any pcall error to Process_UncaughtExceptionEx (consistent with the existing idiom used 100+ times in this codebase). Apply at the four hot spots that have caused fatal-promotion crashes in the field: ILibDuktape_HttpStream.c (server response write paths, including chunked transfer encoding) and ILibDuktape_ReadableStream.c (pipe chain dispatch). (cherry picked from commit f2bfaf2) * mac_kvm: validate peer + cap retries on Tahoe LaunchAgent socket (fix #359) After PR #349 (a20c31f) introduced the user-LaunchAgent relay path (/tmp/meshagent-kvm-<uid>.sock) for macOS Tahoe, the daemon's kvm_relay_setup() wraps an AF_UNIX fd in ILibProcessPipe_Pipe and returns it to the JS engine. The engine then takes the JS-side stream's heapptr, calls .write() on it later from a native callback, and if anything has closed the underlying pipe since the connect(), the next .write() raises 'cannot read property "write" of undefined' on the C side. That error originates from a native callback, so no JS try/catch can catch it, and Duktape promotes it to a fatal that calls ILibDuktape_ScriptContainer_Engine_fatal -> ILIBCRITICALEXITMSG(254). Every 1-12 minutes the agent exits 254, launchd relaunches it, and Remote Desktop never renders. Companion SIGSEGV (KERN_INVALID_ADDRESS at 0x40) is the same family with the heapptr dereferenced unchecked. The actual root cause: stat() reports the socket file as present even after a LaunchAgent crash leaves a stale entry in /tmp, and connect() can return 0 briefly when the agent is mid-shutdown. We then wrap a phantom pipe. Two fixes, layered: 1. Peer credential validation (the actual root cause fix). Immediately after a successful connect(), use getpeereid() to check the peer's uid matches the one we were told to connect as, and getsockopt(SOL_LOCAL, LOCAL_PEERPID) to fetch the peer's pid (Apple-specific since 10.4; a small fallback define guards older SDKs). If the peer doesn't look like the LaunchAgent, close the fd, treat as transient, and retry. The path that caused #359 was a peer that was about to die — its pid was still resolvable but its pipe would not survive the first .write(). 2. Retry hardening. - maxRetries 5 -> 8 (total budget ~3.65s vs ~1.55s; the LaunchAgent can take a couple of seconds to rebind after a session change). - Replace 'usleep(50000 << attempt)' with a small static backoff table capped at 800ms. On 32-bit int, the shift would silently wrap to small sleeps past attempt=4. Not the proximate cause of #359, but the symptom was that high-attempt retries were happening back-to-back with no real backoff, increasing the chance of connecting to a phantom peer. - Add explicit 'giving up' log with the last connect() errno so future debugging doesn't have to reverse-engineer why the socket path returned NULL. - Treat ILibProcessPipe_Pipe_CreateFromExisting returning NULL as a transient retry rather than a hard failure that would fall through to a half-initialized pipe. NOT changed (deliberately): the 'socket exists but unreachable -> return NULL' policy from PR #349. Falling back to fork-exec on Tahoe lands the helper in the wrong audit session and produces black-screen / dead-input. Returning NULL and letting the caller surface the error to MeshCentral is the correct behavior; the improvement here is that, with peer validation, we no longer return NULL *after* having wrapped a phantom pipe. Bump MESH_VER 194 -> 195 (makefile:195) per the protocol-visible- change policy. The peer-validation step is local to the daemon and does not change the wire format, but the LaunchAgent fallback semantics and retry budget do affect the agent's lifecycle behavior and are worth flagging on the wire. Files: - meshcore/KVM/MacOS/mac_kvm.c: includes, new helper kvm_relay_socket_validate_peer(), modified kvm_relay_setup(). - makefile: MESH_VER 194 -> 195. Verification: read-only review on Linux host. Build for macOS ('make macos ARCHID=16|29') requires Xcode SDK and cannot run on this host. Reviewer must run a Mac build + a Tahoe KVM session-replay test before merge. Companion commit: f2bfaf2 (ILibDuktape_SafePcallMethod) which turns the fatal-promotion crash from a hard exit(254) into a soft uncaughtException on the rare paths where the heapptr goes stale anyway. (cherry picked from commit 2c68429) * ci: add macOS build smoke test for #359 Triggered on push to the fix branch and on PRs to master that touch any of the files modified by the fix. Builds ARCHID=16 (x86_64) on macos-13 and ARCHID=29 (arm64) on macos-14 with 'make macos', uploads the binaries as artifacts. Intentionally narrow: it does not start the agent or attempt to reproduce the runtime crash; the build itself is the evidence that the new kvm_relay_socket_validate_peer() helper, the modified kvm_relay_setup() retry loop, and the B-side ILibDuktape_SafePcallMethod changes compile and link on real Apple toolchains. (cherry picked from commit eb44059) * ci: switch x86_64 macos runner to macos-13-large (macos-13 queue saturated) The previous macos-13 job for the x86_64 build remained queued for 30+ minutes after the arm64 (macos-14) job had already finished in under a minute. macos-13 is the last Intel-based image and its runner pool is more constrained than the Apple Silicon pool. Switch to macos-13-large (same OS, more resources, often a different runner pool) to unblock the workflow. (cherry picked from commit 4100161) * ci(macos): use GitHub-hosted macos-13 (not macos-13-large) macos-13-large is a self-hosted-only label; jobs requesting it on a GH-hosted fork stay queued forever (observed: 30+ min on x86_64 job of run 2, runner_id never assigned). Switch back to the standard macos-13 GitHub-hosted label. If the standard macos-13 queue is ever saturated in the future, the only valid alternative for x86_64 is to switch to a self-hosted runner (documented inline). (cherry picked from commit b02635c) * ci(macos): address review on #362 and unblock the x86_64 job Two changes, both CI-only. No functional change to the fix itself. 1. Review feedback from @si458 on #362: drop `paths` and the branch-pinned `push` trigger, leaving just `pull_request: branches: [master]`, so the workflow matches the other build workflows in this repo and Actions rebuilds a binary on every push to a PR (the `pull_request` event re-fires on `synchronize`). 2. The x86_64 job could never have produced that binary: it targeted `macos-13`, the last Intel image, which GitHub is retiring. Jobs requesting it are not assigned a runner and get cancelled by the 24h queue timeout -- in @fsmw's fork that job was cancelled exactly 24h after it started, and upstream this PR has zero check runs to this day. It now cross-compiles from an Apple silicon runner, which the makefile already supports: ARCHID=16 builds with `CC = gcc -arch x86_64`, and both third-party dependencies are checked in per architecture (lib-jpeg-turbo/macos/osx-x86-64/libturbojpeg.a and openssl/libstatic/macos/osx-x86-64), so nothing in the build depends on the runner's own architecture. The previous comment in the workflow said a self-hosted runner was the only alternative; it is not, and the comment is corrected. * Remove macOS PR build/test workflow per reviewer request (#380) --------- Co-authored-by: Fernando <fsanmartin@iternova.cl>
Only setImmediate was pinned in the heap stash, so setTimeout/setInterval objects the caller didn't retain/reference could be GC'd and silently cancelled by their finalizer. This made unreferenced setTimout/setIntervals unreliable. Fix: Root all timers at init and release on elapse/clear.
* Build the system TLS trust store once per process, lazy where possible, shared with every SSL_CTX. Robust certpath lookup instead of shelling out to openssl for every context.
The vendor OpenSSL's compiled-in path is /usr/local/ssl, which no distribution normally uses. Every util_load_system_certs() call therefore found X509_get_default_cert_dir() pointing at a missing directory and fell back to popen("openssl version -d") — a shell, resolved through $PATH, once per SSL_CTX. The original code even carried a comment calling this a security risk.
macOS now reads the Keychain anchors directly (Security.framework), just like Windows reading the system ROOT store (default anchor set only; admin trust overrides are not consulted).
Linux has no standard mechanism, so the unofficial one is implemented:
SSL_CERT_FILE/SSL_CERT_DIR env vars first
well-known rehashed cert directories, validated, registered lazily
well-known CA bundles (loaded in full)
last resort: 'openssl version -d' via execve from absolute paths - no shell, minimal env, 3s timeout - then OpenSSL's own defaults.
Side note: stock agent to server TLS never consults this store (the server is pinned; meshcore.js disables chain verification everywhere), so even total failure isn't fatal. It only matters for custom scripts verifying against public CAs.
The real gain is one store, built once, shared by reference. On Windows (lazy by-subject ROOT-store lookup) and Linux with a rehashed dir it stays empty until a handshake requests an issuer. MacOS and bundle-only systems load all, once.
Previously every SSL_CTX built its own: a full system-store copy on Windows, the shell-out on Linux/macOS.
* Make macos 10.12 (Sierra) the minimum
Needed for functions used
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements