Skip to content

fix(constant-time-analysis): restore dis offset column on Python 3.13+ - #200

Open
farhan6667 wants to merge 1 commit into
trailofbits:mainfrom
farhan6667:fix/ct-analyzer-python313-dis-offsets
Open

fix(constant-time-analysis): restore dis offset column on Python 3.13+#200
farhan6667 wants to merge 1 commit into
trailofbits:mainfrom
farhan6667:fix/ct-analyzer-python313-dis-offsets

Conversation

@farhan6667

Copy link
Copy Markdown

Summary

PythonAnalyzer in plugins/constant-time-analysis/ct_analyzer/script_analyzers.py calls python -m dis <file> and parses the output with a regex that requires a numeric byte-offset column before each instruction mnemonic.

Python 3.13 stopped printing that offset column by default (see What's New in Python 3.13) and added -O/--show-offsets to opt back in. Without that flag on 3.13+, every instruction line in _parse_dis_output silently fails to match: total_instructions stays 0 and the analyzer reports "PASSED, no violations" on Python code with genuine timing side-channels.

Reproduced with the plugin's own bundled sample:

# Before this fix, on Python 3.13:
$ python3 ct_analyzer/analyzer.py ct_analyzer/tests/test_samples/vulnerable.py --warnings
...
Functions analyzed: 17
Instructions analyzed: 0
No violations found.
Result: PASSED

The same is true for a minimal early-exit secret comparison (the canonical example this tool exists to catch):

def check_api_key(provided_key, real_key):
    if len(provided_key) != len(real_key):
        return False
    for a, b in zip(provided_key, real_key):
        if a != b:
            return False
    return True

Fix

_get_dis_output now detects whether --show-offsets is supported (via python -m dis --help) and passes it only when available, so behavior on Python < 3.13 (which doesn't have the flag) is unchanged.

After the fix, on Python 3.13:

Functions analyzed: 17
Instructions analyzed: 328
...
[WARN] COMPARE_OP ... Reason: COMPARE_OP may early-terminate on secret data
[WARN] BINARY_OP_MODULO ...
Result: PASSED
Errors: 0, Warnings: 12

Testing

  • Added test_get_dis_output_includes_offsets_on_py313_plus, a regression test that fails without this fix on Python 3.13+ (skips itself on older Python where --show-offsets doesn't exist).
  • Full suite: python3 -m pytest ct_analyzer/tests/test_analyzer.py — 59 passed, 1 pre-existing failure unrelated to this change (test_cross_compile_arm64 fails in my sandbox due to missing local glibc headers for arm64 cross-compilation, not related to the Python analyzer).
  • ruff check and ruff format --diff both clean on the two changed files.

Note on the existing integration test

test_python_vulnerable_detected already exercises this exact path but its assertion is if report.error_count > 0: ..., which passes vacuously when error_count stays 0 — that's how this false negative shipped without CI catching it. I left that test as-is to keep this PR focused on the root-cause fix plus one new regression test that would actually have caught it; happy to also tighten that assertion in this PR or a follow-up if maintainers prefer.

Python 3.13 stopped printing the bytecode byte-offset column in
`python -m dis` output by default (see What's New in Python 3.13),
adding `-O`/`--show-offsets` to opt back in. PythonAnalyzer's
_parse_dis_output regex requires that offset column, so on 3.13+
every instruction line silently failed to match: total_instructions
stayed at 0 and the analyzer reported "PASSED, no violations" on
code with genuine timing side-channels (e.g. an early-exit
byte-by-byte comparison on secret data).

_get_dis_output now detects --show-offsets support at runtime and
passes it only when available, so it keeps working unmodified on
Python < 3.13 where the flag doesn't exist.

Verified against the bundled tests/test_samples/vulnerable.py sample:
before this change it reported 0 instructions analyzed and "PASSED";
after, it correctly reports 328 instructions and multiple violations.
Added a regression test that would have caught this.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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.

2 participants