8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic - #32494
8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic#32494zangcq wants to merge 5 commits into
Conversation
The CH1_LOOP in string_indexof_char uses an 8-byte 'ld' to scan characters via SWAR, deliberately over-reading up to 7 bytes past the end of the string (the trailing garbage is masked out by the HIT bounds check). The head-alignment code that rounds str1 down to an 8-byte boundary was guarded by 'if (AvoidUnalignedAccesses)'. When AvoidUnalignedAccesses is false (the default on most RISC-V profiles), the alignment was skipped and str1 stayed only 4-byte aligned. If the string's trailing bytes sat right before an unmapped page, the last 8-byte load straddled the page boundary and crashed with SIGSEGV (SEGV_ACCERR) at 'ld t0, 0(a1)'. This crash is layout-dependent and intermittent (~7% in local runs of JImageOpenTest); it manifested as SIGSEGV in C2-compiled String.indexOf and JrtPath.initOffsets, failing VarHandleTestMethodHandleAccessDouble/Float and JImageOpenTest in tier1. Make the head alignment unconditional. Aligning str1 to 8 bytes guarantees every 8-byte load in CH1_LOOP stays within a single page, so the intentional over-read can never step into an adjacent unmapped page, regardless of AvoidUnalignedAccesses. Verified: JImageOpenTest 0/150 crashes after the fix (was ~7% before); VarHandleTestMethodHandleAccessDouble/Float and JImageOpenTest pass under jtreg; no regression with -XX:+AvoidUnalignedAccesses.
|
👋 Welcome back zangcq! A progress list of the required criteria for merging this PR into |
|
@zangcq This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 37 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@DingliZhang, @RealFYang) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
|
The total number of required reviews for this PR has been set to 2 based on the presence of this label: |
|
@zangcq : Hi, I've just create a new JBS for this: https://bugs.openjdk.org/browse/JDK-8391041 |
|
Thanks for finding this. But I don't think it's good for this intrinsic to do out-of-bounds access. For better readability, this also renamed |
thanks for your advice,I just test pass the tier1. let me try your pacth for this issue. |
|
/issue JDK-8391041 |
|
@RealFYang Only the author (@zangcq) is allowed to issue the |
|
/issue JDK-8391041 |
|
@zangcq The primary solved issue for a PR is set through the PR title. Since the current title does not contain an issue reference, it will now be updated. |
Webrevs
|
|
PS: There isn't notable change in JMH numbers ( |
Replace the unconditional head-alignment fix with the approach that never over-reads: the SWAR CH1_LOOP now only consumes full 8-byte blocks (bge cnt1, 8), and the trailing 1..7 chars are handled by string_indexof_char_short. This keeps every 8-byte load inside the string regardless of AvoidUnalignedAccesses, so it can never step into an adjacent unmapped page. Two subtleties in the tail path: - string_indexof_char_short returns an index relative to the current str1, which the SWAR loop has already advanced; add back the scanned prefix (orig_cnt - cnt1) to recover the absolute index. - ch was broadcast across all 8 bytes for the SWAR loop, but the short helper compares a single element, so restore ch to a single char (zext_b / zext 16) before calling it. Without this the tail characters never match and indexOf wrongly returns -1. Verified: tier1 passes (hotspot/jdk/langtools/lib-test, 0 failures), including TestStringLatin1IndexOfChar and TestStringIndexOfCharIntrinsics for both Latin1 and UTF16.
- Introduce a dedicated start_index register alias (tmp4) instead of reusing mask1 (tmp3) for the short-path prefix, making the intent clear. - Drop the redundant j(SHORT); the tail path now falls through to SHORT. - Use the symmetric zext(ch, ch, 8) form for Latin1 (equivalent to zext_b). No functional change; TestStringLatin1IndexOfChar, TestStringIndexOfCharIntrinsics and TestStringIntrinsics2 still pass.
RealFYang
left a comment
There was a problem hiding this comment.
Thanks. Latest version looks good. BTW: I can sponsor if you type /integrate.
|
/integrate |
|
/sponsor |
|
Going to push as commit 660dd42.
Your commit was automatically rebased without conflicts. |
|
@RealFYang @zangcq Pushed as commit 660dd42. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Background / Root cause
The
CH1_LOOPmain loop ofstring_indexof_char(the_indexOfCharintrinsic, i.e.StringLatin1.indexOfChar/StringUTF16.indexOfChar) uses an 8-byteldto scan characters via SWAR, deliberately over-reading up to 7 bytes past the end of thebyte[](the trailing garbage is masked out, so the result is still correct).The head-alignment code that rounds
str1down to an 8-byte boundary was guarded byif (AvoidUnalignedAccesses). WhenAvoidUnalignedAccesses=false(the default on most RISC-V profiles) the alignment was skipped andstr1stayed only 4-byte aligned. If the string's trailing bytes happened to sit right before an unmapped page, the last 8-byteldstraddled the page boundary and crashed withSIGSEGV (SEGV_ACCERR)atld t0, 0(a1).The crash is heap-layout dependent and intermittent (~7% in local runs of
JImageOpenTest). It manifested as SIGSEGV in C2-compiledString.indexOfandJrtPath.initOffsets, failingVarHandleTestMethodHandleAccessDouble/FloatandJImageOpenTestin tier1 (exit code 134).This has existed since the RISC-V port landed via JEP 422 (JDK-8276799) and is still unfixed in upstream master.
Fix
Instead of relying on alignment to keep the intentional over-read inside a page, rework the loop so it never over-reads at all:
CH1_LOOPnow only consumes full 8-byte blocks (bge cnt1, loop_step, CH1_LOOP). Every 8-byteldis therefore fully backed by the string data and can never touch an adjacent page, regardless of alignment orAvoidUnalignedAccesses.string_indexof_char_short, reusing theSHORTpath already taken by short inputs.Two subtleties in the shared short path:
string_indexof_char_shortreturns an index relative to the currentstr1, which the SWAR loop has already advanced. The number of chars already scanned (orig_cnt - cnt1) is passed into the helper as a newstart_indexargument and added back to the match index, so the returned index is absolute. The head-alignment / short-input entries passstart_index = 0.chrestore —chwas broadcast across all 8 bytes for the SWAR loop, but the short helper compares a single element, sochis zero-extended back to a single char (zext(ch, ch, 8/16)) before the tail call. Without this the tail characters never match andindexOfwrongly returns -1.The old
if (AvoidUnalignedAccesses)head-alignment block is kept purely as a throughput optimization; it no longer has any correctness responsibility.Files
c2_MacroAssembler_riscv.cpp— reworkstring_indexof_char; addstart_indexparam + rebasing tostring_indexof_char_shortc2_MacroAssembler_riscv.hpp—string_indexof_char_shortsignature (newstart_index)Verification
Rebuilt hotspot + jdk-image, then ran the intrinsic tests and full tiers. A dedicated fuzz harness (Latin1 + UTF16, lengths 1..2048, search char in every position incl. the final char of the last partial 8-byte block) matched the non-intrinsic reference exactly.
Intrinsic tests:
TestStringLatin1IndexOfChar,TestStringIndexOfCharIntrinsics,TestStringIntrinsics2— all Passed (Latin1 + UTF16).Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/32494/head:pull/32494$ git checkout pull/32494Update a local copy of the PR:
$ git checkout pull/32494$ git pull https://git.openjdk.org/jdk.git pull/32494/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 32494View PR using the GUI difftool:
$ git pr show -t 32494Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/32494.diff
Using Webrev
Link to Webrev Comment