{"id":"GHSA-5cxw-w2xg-2m8h","summary":"fickling's `platform` module subprocess invocation evades `check_safety()` with `LIKELY_SAFE`","details":"# Our assessment\n\nWe added `platform` to the blocklist of unsafe modules (https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975). \n\nIt was not possible to inject extra arguments to `file` without first monkey-patching `platform._follow_symlinks` with the pickle, as it always returns an absolute path. We independently hardened it with https://github.com/trailofbits/fickling/commit/b9e690c5a57ee9cd341de947fc6151959f4ae359 to reduce the risk of obtaining direct module references while evading detection.\n\nhttps://github.com/python/cpython/blob/6d1e9ceed3e70ebc39953f5ad4f20702ffa32119/Lib/platform.py#L687-L695\n```python\ntarget = _follow_symlinks(target)\n# \"file\" output is locale dependent: force the usage of the C locale\n# to get deterministic behavior.\nenv = dict(os.environ, LC_ALL='C')\ntry:\n    # -b: do not prepend filenames to output lines (brief mode)\n    output = subprocess.check_output(['file', '-b', target],\n                                     stderr=subprocess.DEVNULL,\n                                     env=env)\n```\n\n# Original report\n\n## Summary\nA crafted pickle invoking `platform._syscmd_file`, `platform.architecture`, or `platform.libc_ver` passes `check_safety()` with `Severity.LIKELY_SAFE` and zero findings. During `fickling.loads()`, these functions invoke `subprocess.check_output` with attacker-controlled arguments or read arbitrary files from disk.\n\n**Clarification:** The subprocess call uses a list argument (`['file', '-b', target]`), not `shell=True`, so the attacker controls the file path argument to the `file` command, not the command itself. The impact is subprocess invocation with attacker-controlled arguments and information disclosure (file type probing), not arbitrary command injection.\n\n## Affected versions\n`\u003c= 0.1.9` (verified on upstream HEAD as of 2026-03-04)\n\n## Non-duplication check against published Fickling GHSAs\nNo published advisory covers `platform` module false-negative bypass. This follows the same structural pattern as GHSA-5hwf-rc88-82xm (missing modules in `UNSAFE_IMPORTS`) but covers a distinct set of functions.\n\n## Root cause\n1. `platform` not in `UNSAFE_IMPORTS` denylist.\n2. `OvertlyBadEvals` skips calls imported from stdlib modules.\n3. `UnusedVariables` heuristic neutralized by making call result appear used (`SETITEMS` path).\n\n## Reproduction (clean upstream)\n```python\nfrom unittest.mock import patch\nimport fickling\nimport fickling.fickle as op\nfrom fickling.fickle import Pickled\nfrom fickling.analysis import check_safety\n\npickled = Pickled([\n    op.Proto.create(4),\n    op.ShortBinUnicode('platform'),\n    op.ShortBinUnicode('_syscmd_file'),\n    op.StackGlobal(),\n    op.ShortBinUnicode('/etc/passwd'),\n    op.TupleOne(),\n    op.Reduce(),\n    op.Memoize(),\n    op.EmptyDict(),\n    op.ShortBinUnicode('init'),\n    op.ShortBinUnicode('x'),\n    op.SetItem(),\n    op.Mark(),\n    op.ShortBinUnicode('trace'),\n    op.BinGet(0),\n    op.SetItems(),\n    op.Stop(),\n])\n\nresults = check_safety(pickled)\nprint(results.severity.name, len(results.results))  # LIKELY_SAFE 0\n\nwith patch('subprocess.check_output', return_value=b'ASCII text') as mock_sub:\n    fickling.loads(pickled.dumps())\n    print('subprocess called?', mock_sub.called)       # True\n    print('args:', mock_sub.call_args[0])               # (['file', '-b', '/etc/passwd'],)\n```\n\nAdditional affected functions (same pattern):\n- `platform.architecture('/etc/passwd')` — calls `_syscmd_file` internally\n- `platform.libc_ver('/etc/passwd')` — opens and reads arbitrary file contents\n\n## Minimal patch diff\n```diff\n--- a/fickling/fickle.py\n+++ b/fickling/fickle.py\n@@\n+        \"platform\",\n```\n\n## Validation after patch\n- Same PoC flips to `LIKELY_OVERTLY_MALICIOUS`\n- `fickling.loads` raises `UnsafeFileError`\n- `subprocess.check_output` is not called\n\n## Impact\n- **False-negative verdict:** `check_safety()` returns `LIKELY_SAFE` with zero findings for a pickle that invokes a subprocess with attacker-controlled arguments.\n- **Subprocess invocation:** `platform._syscmd_file` calls `subprocess.check_output(['file', '-b', target])` where `target` is attacker-controlled. The `file` command reads file headers and returns type information, enabling file existence and type probing.\n- **File read:** `platform.libc_ver` opens and reads chunks of an attacker-specified file path.","modified":"2026-03-14T09:18:21.040307Z","published":"2026-03-13T20:58:10Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-184"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-03-13T20:58:10Z"},"references":[{"type":"WEB","url":"https://github.com/trailofbits/fickling/security/advisories/GHSA-5cxw-w2xg-2m8h"},{"type":"WEB","url":"https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975"},{"type":"PACKAGE","url":"https://github.com/trailofbits/fickling"},{"type":"WEB","url":"https://github.com/trailofbits/fickling/releases/tag/v0.1.10"}],"affected":[{"package":{"name":"fickling","ecosystem":"PyPI","purl":"pkg:pypi/fickling"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.1.10"}]}],"versions":["0.0.1","0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 0.1.9","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-5cxw-w2xg-2m8h/GHSA-5cxw-w2xg-2m8h.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"}]}