Skip to content

8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic - #32494

Closed
zangcq wants to merge 5 commits into
openjdk:masterfrom
zangcq:JDK-8390816
Closed

8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic#32494
zangcq wants to merge 5 commits into
openjdk:masterfrom
zangcq:JDK-8390816

Conversation

@zangcq

@zangcq zangcq commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Background / Root cause

The CH1_LOOP main loop of string_indexof_char (the _indexOfChar intrinsic, i.e. StringLatin1.indexOfChar / StringUTF16.indexOfChar) uses an 8-byte ld to scan characters via SWAR, deliberately over-reading up to 7 bytes past the end of the byte[] (the trailing garbage is masked out, so the result is still correct).

The head-alignment code that rounds str1 down to an 8-byte boundary was guarded by if (AvoidUnalignedAccesses). When AvoidUnalignedAccesses=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 happened to sit right before an unmapped page, the last 8-byte ld straddled the page boundary and crashed with SIGSEGV (SEGV_ACCERR) at ld t0, 0(a1).

The crash is heap-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 (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:

  • The SWAR CH1_LOOP now only consumes full 8-byte blocks (bge cnt1, loop_step, CH1_LOOP). Every 8-byte ld is therefore fully backed by the string data and can never touch an adjacent page, regardless of alignment or AvoidUnalignedAccesses.
  • The remaining 1..7 trailing chars are handled by the existing per-element helper string_indexof_char_short, reusing the SHORT path already taken by short inputs.

Two subtleties in the shared short path:

  1. Index rebasingstring_indexof_char_short returns an index relative to the current str1, which the SWAR loop has already advanced. The number of chars already scanned (orig_cnt - cnt1) is passed into the helper as a new start_index argument and added back to the match index, so the returned index is absolute. The head-alignment / short-input entries pass start_index = 0.
  2. ch restorech was broadcast across all 8 bytes for the SWAR loop, but the short helper compares a single element, so ch is zero-extended back to a single char (zext(ch, ch, 8/16)) before the tail call. Without this the tail characters never match and indexOf wrongly 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 — rework string_indexof_char; add start_index param + rebasing to string_indexof_char_short
  • c2_MacroAssembler_riscv.hppstring_indexof_char_short signature (new start_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).

## Tier1 (TEST SUCCESS)

 group     | passed            | failed
-----------+-------------------+--------
 hotspot   | 2,949             | 0
 jdk       | 2,543             | 0
 langtools | 4,719             | 0
 jaxp      | no tests selected | -
 lib-test  | 38                | 0
 total     | 10,249            | 0

## Tier2

 group     | passed | failed
-----------+--------+--------
 hotspot   | 946    | 1
 jdk       | 4,434  | 1
 langtools | 12     | 0
 jaxp      | 516    | 0
 docs      | 4      | 0
 total     | 5,912  | 2

## Tier3

 group     | passed            | failed
-----------+-------------------+--------
 hotspot   | 322               | 2
 jdk       | 1,585             | 0
 langtools | no tests selected | -
 jaxp      | no tests selected | -
 total     | 1,907             | 2


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/32494/head:pull/32494
$ git checkout pull/32494

Update a local copy of the PR:
$ git checkout pull/32494
$ git pull https://git.openjdk.org/jdk.git pull/32494/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 32494

View PR using the GUI difftool:
$ git pr show -t 32494

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/32494.diff

Using Webrev

Link to Webrev Comment

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

bridgekeeper Bot commented Aug 24, 2026

Copy link
Copy Markdown

👋 Welcome back zangcq! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk

openjdk Bot commented Aug 24, 2026

Copy link
Copy Markdown

@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:

8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic

Reviewed-by: dzhang, fyang

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 master branch:

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 /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk Bot added the hotspot-compiler hotspot-compiler-dev@openjdk.org label Aug 24, 2026
@openjdk

openjdk Bot commented Aug 24, 2026

Copy link
Copy Markdown

@zangcq The following label will be automatically applied to this pull request:

  • hotspot-compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk

openjdk Bot commented Aug 24, 2026

Copy link
Copy Markdown

The total number of required reviews for this PR has been set to 2 based on the presence of this label: hotspot-compiler. This can be overridden with the /reviewers command.

@RealFYang

RealFYang commented Aug 25, 2026

Copy link
Copy Markdown
Member

@zangcq : Hi, I've just create a new JBS for this: https://bugs.openjdk.org/browse/JDK-8391041

@RealFYang

RealFYang commented Aug 25, 2026

Copy link
Copy Markdown
Member

Thanks for finding this. But I don't think it's good for this intrinsic to do out-of-bounds access.
I would suggest following reference change which avoids this behaviour.
This has already passed tier1-3 test on my linux-riscv64 platform. Can you try if this resolves your issue?

fix-v1.diff.txt

For better readability, this also renamed trailing_char and unaligned_elems to trailing_chars and unaligned_chars respectively.

@zangcq

zangcq commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for finding this. But I don't think it's good for this intrinsic to do out-of-bounds access. I would suggest following reference change which avoids this behaviour. This has already passed tier1-3 test on my linux-riscv64 platform. Can you try if this resolves your issue?

fix-v1.diff.txt

For better readability, this also renamed trailing_char and unaligned_elems to trailing_chars and unaligned_chars respectively.

thanks for your advice,I just test pass the tier1. let me try your pacth for this issue.

@zangcq zangcq changed the title RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic 8391041:RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic Aug 25, 2026
@RealFYang

RealFYang commented Aug 25, 2026

Copy link
Copy Markdown
Member

/issue JDK-8391041

@openjdk

openjdk Bot commented Aug 25, 2026

Copy link
Copy Markdown

@RealFYang Only the author (@zangcq) is allowed to issue the /issue command.

@zangcq

zangcq commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

/issue JDK-8391041

@openjdk openjdk Bot changed the title 8391041:RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic 8391041: RISC-V: Fix out-of-bounds read in string_indexof_char intrinsic Aug 25, 2026
@openjdk

openjdk Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

@openjdk openjdk Bot added the rfr Pull request is ready for review label Aug 25, 2026
@mlbridge

mlbridge Bot commented Aug 25, 2026

Copy link
Copy Markdown

Webrevs

@RealFYang

Copy link
Copy Markdown
Member

PS: There isn't notable change in JMH numbers (make test TEST="micro:java.lang.StringIndexOfChar") with my suggested change.

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.
Comment thread src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp Outdated
Comment thread src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp Outdated
Comment thread src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp Outdated
Comment thread src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp Outdated
Comment thread src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp Outdated
- 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.

@DingliZhang DingliZhang left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@RealFYang RealFYang left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks. Latest version looks good. BTW: I can sponsor if you type /integrate.

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Aug 26, 2026
@zangcq

zangcq commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

/integrate

@openjdk openjdk Bot added the sponsor Pull request is ready to be sponsored label Aug 26, 2026
@openjdk

openjdk Bot commented Aug 26, 2026

Copy link
Copy Markdown

@zangcq
Your change (at version f4b6cb6) is now ready to be sponsored by a Committer.

@RealFYang

Copy link
Copy Markdown
Member

/sponsor

@openjdk

openjdk Bot commented Aug 26, 2026

Copy link
Copy Markdown

Going to push as commit 660dd42.
Since your change was applied there have been 37 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk Bot added the integrated Pull request has been integrated label Aug 26, 2026
@openjdk openjdk Bot closed this Aug 26, 2026
@openjdk openjdk Bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Aug 26, 2026
@openjdk

openjdk Bot commented Aug 26, 2026

Copy link
Copy Markdown

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotspot-compiler hotspot-compiler-dev@openjdk.org integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

3 participants