Cover the non-RCE behaviour of the new JSON cache:
- round-trip: written file is valid JSON, re-read produces equivalent dict
- legacy pickle: a pre-fix pickle cache is treated as a cache miss, not
a crash (upgrade path)
- expiry: caches older than 7 days are invalidated
- version skew: caches written by a different installed version are
invalidated
- first run: a missing file is not an error
The version-check cache at $XDG_CACHE_HOME/glances/glances-version.db is
read at every Glances startup via pickle.load() — an execution-capable
deserialization format. Any process able to write that path (local user
on a shared host, sibling container on a shared volume, symlink race
during first run) could plant a pickle whose __reduce__ runs arbitrary
code as the Glances user, including root in typical deployments.
Switch to json for both load and save. The stored payload is trivial:
two strings plus a timestamp that round-trips via isoformat(). Any
unreadable, malformed, or legacy-pickle file is caught by the existing
exception handler and treated as a cache miss — the next PyPI refresh
overwrites it with a JSON file. No user-visible behaviour change.
The pickle module is removed from the imports — it has no other use in
this file.
Mitigates CVE-2026-46607.
Regression test for GHSA-9837-48hr-q32j: glances/outdated.py reads its
version-check cache file via pickle.load(), a deserialization format
that executes arbitrary callables embedded via __reduce__.
The test plants a poisoned pickle at the cache path and asserts that
_load_cache() does NOT trigger the embedded callable. Against the
current (vulnerable) code this fails because the payload fires before
the TypeError is raised on the unrelated dict subscript.
The fix in the next commit replaces pickle with json, which is a passive
data format.
Mirrors the existing REST/WebUI warning style. Makes unprotected
XML-RPC deployments visible to operators without changing default
behaviour (no enforcement).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add opt-in DNS rebinding protection to the XML-RPC server via a new
xmlrpc_allowed_hosts config key in [outputs]. When set, the handler
rejects requests whose Host header does not match any of the listed
patterns (fnmatch wildcards supported). Validation runs before
authentication so spoofed Host values are rejected regardless of
credentials.
Default behaviour is unchanged (no allowlist = no filtering). A
startup warning is added in a follow-up commit to make unprotected
deployments visible to operators.
Mitigates CVE-2026-46611.
Adds a second test server bound to a config that enables xmlrpc_allowed_hosts,
plus the failing assertion that a spoofed Host header returns 400. The fix in
glances/server.py follows in the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This test passes on the unpatched server and proves the CVE-2026-46611
vulnerability exists today: a spoofed Host header is accepted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-creates tests/test_xmlrpc.py (deleted symlink) with a pytest module
modelled on test_restful.py: subprocess-launched server and a helper
to POST XML-RPC calls with a controllable Host header. Restores the
existing 'make test-xmlrpc' Makefile target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Design document for CVE-2026-46611 patch: add opt-in Host header
validation to the XML-RPC server via a new xmlrpc_allowed_hosts
config key, with permissive default and startup warning (mirrors
the REST/WebUI mitigation pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Occasionally, columns got misaligned, because auto_unit returned too
many decimals when the number was slightly below 10 or 100.
Actually, when (9.995 <= n < 10) and (99.95 < n < 100).
For example,
10*2**20-1 returned 10.00M instead of 10.0M and
100*2**20-1 returned 100.0M instead of 100M.
Tests added to verify correctness.