From bcbf2f0cd657abb66b1c1ee2ca5d28f8b1265cf1 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 11 Jun 2026 14:30:13 +0100 Subject: [PATCH] crash-decode: do not eval() linker script memory expressions The crash-decode helper evaluated arithmetic from a user-supplied linker script with eval(), so a crafted crash bundle could run arbitrary Python on the analyst's machine. Parse the expressions with a restricted evaluator that only accepts integer literals and basic arithmetic operators. Signed-off-by: Liam Girdwood --- scripts/sof-crash-decode.py | 59 +++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/scripts/sof-crash-decode.py b/scripts/sof-crash-decode.py index 8f76921431be..3691c61d69b6 100755 --- a/scripts/sof-crash-decode.py +++ b/scripts/sof-crash-decode.py @@ -35,6 +35,61 @@ import os import json import shlex +import ast +import operator + + +# Largest shift we accept: linker addresses/sizes fit well under 2**64, so a +# bigger shift is nonsensical and only serves to build a huge integer. +_MAX_SHIFT = 64 + + +def _trunc_div(a, b): + # truncate toward zero (C semantics); Python's // floors instead + q = abs(a) // abs(b) + return -q if (a < 0) != (b < 0) else q + + +def _lshift(a, b): + # reject absurd shift counts to avoid building a massive integer + if b < 0 or b > _MAX_SHIFT: + raise ValueError("shift count out of range") + return a << b + + +# Operators allowed when evaluating arithmetic from an untrusted linker script. +_SAFE_OPS = { + ast.Add: operator.add, ast.Sub: operator.sub, + ast.Mult: operator.mul, ast.Div: _trunc_div, + ast.Mod: operator.mod, ast.LShift: _lshift, + ast.RShift: operator.rshift, ast.BitOr: operator.or_, + ast.BitAnd: operator.and_, ast.BitXor: operator.xor, + ast.USub: operator.neg, ast.UAdd: operator.pos, +} + + +def safe_eval_int(expr): + """Evaluate an integer arithmetic expression without executing code. + + A crash bundle's linker.cmd is attacker-controllable, so its MEMORY + expressions must never be passed to eval(). Only integer literals and + basic arithmetic operators are accepted; anything else raises ValueError. + """ + def _eval(node): + if isinstance(node, ast.Expression): + return _eval(node.body) + if isinstance(node, ast.Constant): + # reject bool (a subclass of int) and everything non-integer + if type(node.value) is int: + return node.value + raise ValueError("non-integer constant") + if isinstance(node, ast.BinOp) and type(node.op) in _SAFE_OPS: + return _SAFE_OPS[type(node.op)](_eval(node.left), _eval(node.right)) + if isinstance(node, ast.UnaryOp) and type(node.op) in _SAFE_OPS: + return _SAFE_OPS[type(node.op)](_eval(node.operand)) + raise ValueError("unsupported expression") + + return _eval(ast.parse(expr, mode='eval')) XTENSA_EXCCAUSE = { 0: "No Error (or IllegalInstruction)", @@ -151,8 +206,8 @@ def parse_linker_cmd(filepath): org_expr = m_org.group(1).strip() len_expr = m_len.group(1).strip() try: - org_val = eval(org_expr) - len_val = eval(len_expr) + org_val = safe_eval_int(org_expr) + len_val = safe_eval_int(len_expr) # Ignore debug regions if not (name.startswith('.debug') or name.startswith('.stab')): regions.append({'name': name, 'start': org_val, 'end': org_val + len_val})