Acknowledged
CIQQA-4579

Simulator segfaults on Communications.transmit over the tethered (ADB) connection — SDK 9.1.0 and 9.2.0, Linux

Summary

With a tethered (ADB) data connection established, the Connect IQ simulator crashes with SIGSEGV the moment a watch app calls Communications.transmit. No byte is written to the socket. Reproducible with a one-line addition to your own samples/Comm, on both SDK 9.1.0 and 9.2.0.

The opposite direction — phone → watch — works correctly over the very same connection, so the transport and the session are demonstrably healthy when the crash happens.

Environment

  • SDK 9.1.0 (2026-03-09) and 9.2.0 (2026-06-09) — both affected
  • Linux (Ubuntu 22.04 container on a Fedora host)
  • Devices tried: vivoactive3 (Comm sample), fr965 (our own app)
  • Peer: a plain TCP server on 127.0.0.1:7381, in place of adb forward tcp:7381 tcp:7381

Minimal reproduction

  1. Listen on TCP 127.0.0.1:7381. Any server will do — this is the host side of adb forward tcp:7381 tcp:7381, and the simulator is the client. Keep it listening for the whole session.
  2. Start the simulator, then Connection → start the ADB connection. The simulator connects.
  3. Take samples/Comm unmodified and add one line to CommApp.onStart:
public function onStart(state as Dictionary?) as Void {
    Communications.transmit("Hello World.", null, new $.CommListener());
}
  1. Run it.

Expected: the payload is serialized and written to the socket.
Actual: the simulator dies with SIGSEGV within a second. Zero bytes are written.

Left unmodified — the sample only transmits from its menu — the same binary stays alive indefinitely while connected.

What we verified is NOT the cause

Each of these was tested and crashes identically:

  • Payload typeString (Comm sample) vs Dictionary (our app): both crash.
  • The options argumentnull (as your sample passes) vs {} (as the docs prescribe): both crash.
  • Stale or invalid session — transmit on a session where a phone → watch message had just been accepted and displayed: crashes.
  • Our own code — reproduces on samples/Comm plus one line.
  • A recent regression — 9.1.0 and 9.2.0 both crash.

Crash analysis

Identical stack across our app and the patched sample (stripped binary, offsets from the ELF base):

#0  simulator + 0xd5fcc8
#1  simulator + 0x40d967
#2  simulator + 0x396ba6

The faulting function:

movl $0x0,(%rsi)      ; *out = 0
test %rdi,%rdi
je   <return>         ; guard 1: null object -> clean return
call <...>            ; ~ buf->data()
mov  %rax,%r12
call <...>            ; ~ buf->size()
cmp  $0x3,%eax
jbe  <bail>           ; guard 2: size <= 3 -> clean return
=> movzwl (%r12),%edx ; SIGSEGV
   movzbl 0x2(%r12),%ecx
   rol   $0x8,%dx
   shl   $0x8,%edx
   or    %ecx,%edx    ; 24-bit big-endian length

Both guards pass — the object is non-null and reports size > 3 — yet %r12 holds 0x8e432fa067e9a719, which is not a mapped address. data() therefore returns a wild pointer while size() returns a plausible value: the buffer object is in an inconsistent state (use-after-free, or read before initialisation). Nothing has been received on the socket at that point, so this is not parsing of peer input.

The opposite direction works, which makes this a narrow bug

To be certain the harness was faithful, we implemented the phone side using the serialization classes shipped in your own companion SDK (ciq-companion-app-sdk, com.garmin.monkeybrains.serialization). Sending a correctly serialized object to the simulator over the same socket works: registerForPhoneAppMessages fires and samples/Comm displays the payload, and the connection stays open.

So: phone → watch is fine; watch → phone segfaults.

Additional observations (same transport, probably related, listed for completeness)

  • If the peer stops listening, the simulator crashes rather than reporting a dropped link.
  • After a few connect/reject cycles, the simulator dies with SIGABRT instead (stack entirely in libc) — a different site.
  • Occasionally the simulator freezes instead of crashing (no coredump, the process must be killed).
  • On both SDKs, the bin/connectiq launcher aborts (SIGABRT) at startup on this machine, while running bin/simulator directly works. Possibly unrelated; mentioned in case it helps.
  • A malformed payload sent phone → watch is answered by an immediate connection close. We believe this explains the long-standing "Get Comm sample working" report ("ADB Disconnected" when messaging the watch): the payload was not a valid serialized object, and the close is the simulator’s rejection — not a transport failure.

Impact

Phone ↔ watch messaging cannot be exercised in the simulator in the watch → phone direction. For developers targeting devices they do not own, the simulator is the only option, so this blocks testing of any app that talks to a companion app.