Skip to content

fix(relinker): reject LOCK-prefixed MOVNTSS and MOVNTSD - #10713

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Filip722:fix/reject-lock-movnts-20261024
Oct 25, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Filip722:fix/reject-lock-movnts-20261024

Conversation

@Filip722

Copy link
Copy Markdown
Contributor

What

LOCK-prefixed MOVNTSS/MOVNTSD currently convert successfully into LOCK-prefixed MOVSS/MOVSD, leaving invalid code that still faults. Reject these encodings in the in-place matcher before generating replacement bytes.

  • Scan only the instruction prefixes, preserving valid operands containing byte F0.
  • Cover both prefix orders for each MOVNT form at converter level and retain the failing file offset.

This is the separate MOVNT follow-up requested in #10340. That PR handles SHA-1, SHA-256, EXTRQ and INSERTQ; the patches are independent and can land in either order.

Tested

Linux x86-64, GCC 15.2.0, Release relinker-only build. The new converter regression fails against unchanged main production code with LOCK MOVNTSS/MOVNTSD was accepted and passes with the matcher guard. The complete relinker-only suite passes.

Native AMD Ryzen 5 7640HS probes: MOVNTSS and MOVNTSD with LOCK before or after their mandatory prefix each raise SIGILL. All four controls without LOCK execute successfully, using valid destination buffers in separate processes. Windows and macOS execution were not rerun for this follow-up.

Manual inspection of the shared-file warnings for #8042, #8236 and #9184 found disjoint scanner and Rosetta execution tests; none changes the MOVNT matcher or these converter regressions.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@MarcZX10 MarcZX10 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.

Reviewed exact head 50dc1c9 against current main. The matcher rejects LOCK only in the instruction prefix bytes before the opcode, while the regression guards against confusing an immediate/displacement byte 0xF0 with a prefix and covers both orders for MOVNTSS/MOVNTSD plus the failing file offset. git diff --check, the current-main conventions check, and merge-tree pass. The relinker test target builds with GCC 12.2, but the registered converter test aborts with SIGILL on this host before reporting results. Linux and ASAN hosted checks pass; Windows and macOS matrix jobs are still running. No blocking source issue found.

@MarcZX10 MarcZX10 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.

Follow-up on exact head 50dc1c9: the complete hosted relinker matrix is now green on Linux, Windows, macOS arm64 and macOS x86_64, with ASAN and policy checks also passing. The earlier review findings and local SIGILL limitation are unchanged; merge state is now CLEAN.

@MarcZX10 MarcZX10 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.

Follow-up on exact head 50dc1c9: the complete hosted relinker matrix is now green on Linux, Windows, macOS arm64 and macOS x86_64, with ASAN and policy checks also passing. The earlier review findings and local SIGILL limitation are unchanged; merge state is now CLEAN.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 8042 core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp
PR 8236 core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp

@MarcZX10 MarcZX10 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.

Reviewed exact head 50dc1c93c8f3095d533ab7cc0b7f64cb0f8cec87. The matcher checks for LOCK only in the decoded prefix span before the opcode, so an F0 displacement/immediate byte is not rejected; the regression cases cover both prefix orders for both mandatory prefixes and assert the original file offset. The change is narrowly placed before replacement generation. Hosted relinker, ASAN, Linux, Windows, macOS arm64 and x86_64 checks pass, with product jobs skipped by policy; the PR reports a native Ryzen SIGILL probe for all four LOCK forms and valid controls. Local amd64_only_converter compiled, but execution on this host terminated with SIGILL, so I could not independently run its assertions. git diff --check and conventions pass, and the PR is mergeable.

@oneandonlydean oneandonlydean self-assigned this Oct 25, 2026

@roboterspinne roboterspinne 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.

Reviewed head 50dc1c9 merged onto upstream/main acbfd7c (2026-10-25); the merge is clean. It is also clean onto the current main 63a863d, whose 11 newer commits touch neither core/relinker nor tools.

Environment: Windows 11 Home 10.0.26200, Intel i9-12900H, WinLibs GCC 15.2.0 UCRT, CMake 4.3.1, Ninja 1.13.2, Python 3.13.9, -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release.

Results

  • Full relinker-only suite on the merged tree: 261 tests, 259 pass, long_paths skipped. The one failure is patch_advisor_unit. It fails the same way on plain main on this machine, because the temp path contains a space (#10667), and it is unrelated. amd64_only_converter and amd64_only_windows pass.

  • Counter-check: with main's Amd64OnlyInstructionMatcher.cpp under this head's test file, amd64_only_converter fails with LOCK MOVNTSS/MOVNTSD was accepted. The harness stops at the first failure, so I also linked the converter once with each matcher and ran the encodings one at a time through Convert, for both the Intel and the Rosetta target. main accepts all four test encodings and writes f0 f3 0f 11 07, f3 f0 0f 11 07, f0 f2 0f 11 07 and f2 f0 0f 11 07. This branch rejects each one with MOVNTSS/MOVNTSD with a LOCK prefix at 0x20F. It also rejects LOCK before or after a REX byte (f0 f3 41 0f 2b 07, f3 41 f0 0f 2b 07), LOCK combined with a segment or address-size prefix, a doubled LOCK and a RIP-relative operand. Both builds still rewrite in place an F0 byte in the displacement or SIB (f3 0f 2b 47 f0, f3 0f 2b 04 f0, f3 0f 2b 87 f0 f0 f0 f0).

  • End to end (--to-intel --skip-syscall-check --skip-sce-module, code at 0x4000): for f0 f3 0f 2b 07, main reports Intel substitution: MOVNTSS at 0x4000 (5 bytes) -> in place 5 bytes and writes f0 f3 0f 11 07. This branch exits 2 with FAIL: MOVNTSS/MOVNTSD with a LOCK prefix (offset 0x4000), and the other three orders behave the same way. The unlocked forms and the F0-displacement form still convert to f3 0f 11 07, f2 0f 11 07 and f3 0f 11 47 f0.

  • Native probe on this Intel CPU (one child process per sequence, xmm0 stored to a valid buffer): the four main outputs, which objdump shows as lock movss and lock movsd, raise STATUS_ILLEGAL_INSTRUCTION (0xc000001d). movss and movsd without LOCK execute and store 4 and 8 bytes. So the description's "still faults" holds on Intel. The AMD side rests on your Ryzen runs.

  • Mutants of the new guard, each run against this test file:

    • Scanning the whole instruction fails at the new displacement case.
    • Checking only the first byte gets past f0 f3 but fails at f3 f0.
    • Checking only the byte before the opcode, or skipping the first prefix byte, fails at f0 f3.

    So both prefix orders and the displacement case each catch a different wrong fix. Returning Unsupported instead of throwing passes, with the same rejection and offset but the message AMD-only instruction without Intel lowering: MOVNTSS (or MOVNTSD). That is an equivalent alternative, not a gap.

  • The converter is the only production caller of the matcher. Its first pass (Amd64OnlyConverter.cpp:334 on main, a file this PR does not change) runs Match inside _atFileOffset on every scanned instruction before any placement code. The error therefore carries the instruction's own offset, and the follower and sequence paths cannot be the first to see a LOCK MOVNT.

  • check_conventions.py --base upstream/main passes with this title and body, and git diff --check is clean. #8042, #8236 and #9184 already conflict with main, and none of their conflict hunks with this head contain its lines. Hosted relinker jobs (ubuntu, windows, both macOS) and ASan are green on 50dc1c9.

Not checked: AMD hardware (your Ryzen probes cover that side). Linux and macOS locally: on MinGW the execution tests in this file are compiled out (#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__)), so my Windows run covers the matcher and converter assertions only. The Rosetta target only through the converter probe above, not with a macOS run.

I found nothing to change. The guard scans exactly the prefix run that IsMovntss and IsMovntsd skip. It therefore refuses LOCK in every position and never mistakes an operand byte for LOCK, and the tests catch each partial fix I tried. It is also the coverage oneandonlydean asked for on #10340: a prefix-order regression for each MOVNT form at converter level, with the failing offset. For disclosure: I suggested this guard on #10340 (the inline comment on Sse4aOperands.cpp), and I have open relinker PRs (#10752, #10732, #10731, #10730, #10712, #10679, #10646, #10607, #10596, #10564, #10403, #10402, #10401, #10391, #10390) plus #10667 on tools/tests/test_suggest_patches.py; none touches core/relinker/codegen/.

Written with AI assistance (Claude Code). I ran the builds, tests, probes and end-to-end conversions above on my Windows 11 machine.

@oneandonlydean
oneandonlydean merged commit d7493d7 into boykopovar:main Oct 25, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants