Skip to content

scripts: do not eval() linker script expressions in crash-decode#10928

Open
lgirdwood wants to merge 1 commit into
thesofproject:mainfrom
lgirdwood:fix-crash-decode
Open

scripts: do not eval() linker script expressions in crash-decode#10928
lgirdwood wants to merge 1 commit into
thesofproject:mainfrom
lgirdwood:fix-crash-decode

Conversation

@lgirdwood

Copy link
Copy Markdown
Member

The crash-decode helper evaluated arithmetic read from a user-supplied
linker script (linker.cmd) with eval(), so a maliciously crafted
linker.cmd in a shared crash bundle could execute arbitrary Python on the
analyst's machine. Parse the MEMORY expressions with a small restricted
evaluator that only accepts integer literals and basic arithmetic
operators, rejecting anything else.

Copilot AI review requested due to automatic review settings June 16, 2026 14:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR removes the use of Python eval() when parsing attacker-controlled arithmetic from linker.cmd in the crash-decode helper, replacing it with a restricted AST-based integer expression evaluator to prevent arbitrary code execution.

Changes:

  • Added safe_eval_int() that parses expressions with ast.parse(..., mode="eval") and evaluates only a whitelisted set of arithmetic operators.
  • Replaced eval(org_expr) / eval(len_expr) with safe_eval_int(...) in linker script parsing.

Comment on lines +63 to +66
if isinstance(node, ast.Constant):
if isinstance(node.value, int):
return node.value
raise ValueError("non-integer constant")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the constant check is now type(node.value) is int, so booleans (a subclass of int) are rejected as non-integers.

Comment on lines +43 to +50
_SAFE_OPS = {
ast.Add: operator.add, ast.Sub: operator.sub,
ast.Mult: operator.mul, ast.Div: operator.floordiv,
ast.Mod: operator.mod, ast.LShift: operator.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,
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed — ast.Div now maps to a truncate-toward-zero helper (C/linker semantics) instead of Python's floor //. e.g. -7 / 2 evaluates to -3, not -4.

return _SAFE_OPS[type(node.op)](_eval(node.operand))
raise ValueError("unsupported expression")

return _eval(ast.parse(expr, mode='eval'))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller already handles this: parse_linker_cmd() wraps both safe_eval_int() calls in try: ... except Exception: pass, so a SyntaxError from ast.parse(), a ZeroDivisionError, or a RecursionError just causes that MEMORY region to be skipped rather than aborting the tool. So the simple "invalid expression -> ignore" failure mode is already in place.

Comment on lines +67 to +70
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))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a guard — left shifts are now capped at 64 bits (_MAX_SHIFT), which rejects things like 1 << 100000000 before a huge integer is built. Linker addresses/sizes are well under 2**64, so this doesn't restrict valid input. Deep nesting / large literals are bounded in practice by the small regex-extracted org/len expressions, and any RecursionError/MemoryError is caught by the caller (see below).

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 <liam.r.girdwood@linux.intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants