linuxmint / linuxmint/cinnamon

Network applet: null SSID on resume crashes `_createSection`, leaking signal handlers into an unbounded GJS GC-callback log storm that freezes the desktop

Open Beginner friendly
#13,994 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
5.6k
Forks
915
Avg merge
5d 22h
Merged PRs (30d)
3

Description

Summary

After suspending and resuming my laptop, and typing my password, the screen went black forever,
only the mouse cursor visible. The LLM-aided analysis of the issue is provided below.

A TypeError: ssid is null in the network applet aborts a menu rebuild partway through,
leaving Clutter actors and NetworkManager signal handlers connected but unreachable from JS.
GJS then emits "Attempting to run a JS callback during garbage collection" plus a stack dump
on every subsequent collection, forever. On my machine this produced 409 million log
messages in 65 minutes
(peak ~87,000/s) and left the desktop black with a live mouse
cursor until Cinnamon was killed manually.

The crash itself is a one-line null dereference. The consequence is a full desktop
denial-of-service that a normal user cannot diagnose -- the screen is black, so there is no
way to reach a terminal without switching to a text VT.

Versions

  • Cinnamon 6.0.4-4 (Ubuntu 24.04, x86_64)
  • Xorg 21.1.11, session type x11, muffin/Mutter 6.0.1
  • Kernel 6.17.0-1030-oem, Intel Meteor Lake (i915)
  • NetworkManager, wifi device wlp0s20f3

The relevant code is unchanged in current master (checked 2026-09-13).

How it triggers

Suspend the laptop with wifi associated, then resume. On resume NetworkManager tears the
wifi device down and back up:

device (wlp0s20f3): state change: unmanaged -> unavailable (reason 'managed', ...)

and re-scans. This is not reliably reproducible on demand -- it depends on the applet's
retained access-point list going stale across the device teardown -- but the failing path is
deterministic once an AP in a retained group returns null from get_ssid().

Evidence

The crash, 17 times within 10 seconds of resume:

szept 13 10:17:20 cinnamon[6907]: JS ERROR: TypeError: ssid is null
  ssidToLabel@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:88:17
  _init@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:114:21
  NMNetworkMenuItem@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:95:16
  _createNetworkItem@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:1662:26
  _createSection@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:1708:18
  _deviceStateChanged@/usr/share/cinnamon/applets/network@cinnamon.org/applet.js:673:14

Ten seconds later the GC storm starts and runs for 65 minutes. Message counts from a single
journal capture:

1847727  cinnamon.desktop[6907]: == Stack trace for context 0x613560320170 ==
 944071  cinnamon[6907]: Attempting to run a JS callback during garbage collection...
 944019  cinnamon[6907]: The offending callback was SourceFunc().
    951  cinnamon[6907]: Attempting to call back into JSAPI during the sweeping phase of GC...

Plus what journald dropped on top of that:

222 suppression events, 408.901.590 messages dropped

The dangling handlers are overwhelmingly the network applet's own:

210  window-monitor-changed on MetaDisplay
140  access-point-added on NMDeviceWifi
102  notify on NMAccessPoint
 94  access-point-removed on NMDeviceWifi
 94  g-signal on GDBusProxy

Effect: the main loop spent all its time formatting and writing log messages, so muffin never
composited a frame. All X clients stayed alive and mapped (_NET_CLIENT_LIST intact, every
window IsViewable) -- the screen was simply black. After 65 minutes the JS context collapsed
entirely and the process went silent at 0% CPU, still holding WM_S0, so cinnamon --replace
hung waiting for the selection and the process had to be SIGKILLed.

Not a GPU fault: the i915 resume is clean, no hang, reset or error on any DRM line.

Analysis

Every entry point into the network list guards against a null SSID:

applet.js:1144 -- building the initial list:

if (ap.get_ssid() == null) {
    // hidden access point cannot be added, ...
    ap._notifySsidId = ap.connect('notify::ssid', Lang.bind(this, this._notifySsidCb));
    continue;
}

applet.js:1389 -- _accessPointAdded, and applet.js:1378 -- _findNetwork: same pattern.

But nothing re-validates an AP after it has been admitted to obj.accessPoints. The consumer
is unguarded -- _createNetworkItem, applet.js:1723:

apObj.item = new NMNetworkMenuItem(apObj.accessPoints);

which reaches NMNetworkMenuItem._init, applet.js:107-113:

accessPoints = sortAccessPoints(accessPoints);
this.bestAP = accessPoints[0];

if (!title) {
    let ssid = this.bestAP.get_ssid();
    title = ssidToLabel(ssid);
}

and ssidToLabel, applet.js:85:

function ssidToLabel(ssid) {
    let label = NM.utils_ssid_to_utf8(ssid.get_data());   // ssid may be null
    if (!label)
        label = _("<unknown>");
    return label;
}

Two things make the guards insufficient:

  1. An AP admitted with a valid SSID can later return null -- across a device teardown on
    resume, the applet keeps its accessPoints arrays while the underlying NM AP objects go
    stale.
  2. sortAccessPoints orders by signal strength, so bestAP is not necessarily the AP that
    was validated at insertion time. Any AP in the group can become the one dereferenced.

Note that ssidToLabel already has an "unknown network" fallback for the case where
utils_ssid_to_utf8 yields nothing -- it just never considers that the SSID object itself
may be absent.

The escalation from "one menu item fails" to "desktop unusable for an hour" is the part worth
addressing beyond the null check: an exception thrown mid-_createSection abandons
already-constructed menu items without destroy(), and GJS has no rate limiting on the
resulting GC diagnostics.

Proposed fix

Minimal, and consistent with the function's existing intent:

function ssidToLabel(ssid) {
    let label = ssid ? NM.utils_ssid_to_utf8(ssid.get_data()) : null;
    if (!label)
        label = _("<unknown>");
    return label;
}

Worth considering in addition:

  • Skip access points whose SSID has become null when rebuilding in _createNetworkItem /
    NMNetworkMenuItem._init, rather than labelling them <unknown>, so stale APs drop out of
    the list instead of accumulating.
  • Wrap the per-item work in _createSection so one failing item cannot abandon the rest of
    the rebuild with actors still connected.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in applet.js at ssidToLabel, NMNetworkMenuItem._init, _createNetworkItem, and _createSection, then trace the existing null-SSID guards and resume-triggered rebuild path. Reproduce the suspend/resume scenario if possible and inspect the Cinnamon logs. Done means a stale access point no longer raises the reported TypeError or leaves the desktop in the described GC log storm.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
desktop, networking
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.