Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8333639: ubsan: cppVtables.cpp:81:55: runtime error: index 14 out of bounds for type 'long int [1]' #19623

Closed

Conversation

TheRealMDoerr
Copy link
Contributor

@TheRealMDoerr TheRealMDoerr commented Jun 10, 2024

We shouldn't specify a wrong array length which causes undefined behavior. Using a "flexible array member".


Progress

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

Issue

  • JDK-8333639: ubsan: cppVtables.cpp:81:55: runtime error: index 14 out of bounds for type 'long int [1]' (Bug - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 19623

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

Using diff file

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

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jun 10, 2024

👋 Welcome back mdoerr! 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
Copy link

openjdk bot commented Jun 10, 2024

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

8333639: ubsan: cppVtables.cpp:81:55: runtime error: index 14 out of bounds for type 'long int [1]'

Reviewed-by: aboldtch, mbaesken, kbarrett

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 83 new commits pushed to the master branch:

  • e95f092: 8333964: RISC-V: C2: Check "requires_strict_order" flag for floating-point add reduction
  • ba5a467: 8332854: Unable to build openjdk with --with-harfbuzz=system
  • 801bf15: 8332105: Exploded JDK does not include CDS
  • c94af6f: 8333962: Obsolete OldSize
  • cdf22b1: 8326715: ZGC: RunThese24H fails with ExitCode 139 during shutdown
  • ef7923e: 8334078: RISC-V: TestIntVect.java fails after JDK-8332153 when running without RVV
  • 0d1080d: 8331117: [PPC64] secondary_super_cache does not scale well
  • 113a2c0: 8332903: ubsan: opto/output.cpp:1002:18: runtime error: load of value 171, which is not a valid value for type 'bool'
  • d751441: 8330586: GHA: Drop additional gcc/glibc packages installation for x86_32
  • 5e09397: 8334222: exclude containers/cgroup/PlainRead.java
  • ... and 73 more: https://git.openjdk.org/jdk/compare/7b43a8cd7c663facbe490f889838d7ead0eba0f9...master

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.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the rfr Pull request is ready for review label Jun 10, 2024
@openjdk
Copy link

openjdk bot commented Jun 10, 2024

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

  • hotspot-runtime

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 added the hotspot-runtime hotspot-runtime-dev@openjdk.org label Jun 10, 2024
@mlbridge
Copy link

mlbridge bot commented Jun 10, 2024

Webrevs

@MBaesken
Copy link
Member

This fixes the ubsan warning.
However we have methods like
intptr_t* cloned_vtable() { return &_cloned_vtable[0]; }
accessing the first element; are you sure that this always works ?

@TheRealMDoerr
Copy link
Contributor Author

This fixes the ubsan warning. However we have methods like intptr_t* cloned_vtable() { return &_cloned_vtable[0]; } accessing the first element; are you sure that this always works ?

Yes, it's a flexible array member. I'm not changing the allocation.

@MBaesken
Copy link
Member

it seems to break the Windows build

d:\a\jdk\jdk\src\hotspot\share\cds\cppVtables.cpp(69): error C2220: the following warning is treated as an error
d:\a\jdk\jdk\src\hotspot\share\cds\cppVtables.cpp(69): warning C4200: nonstandard extension used: zero-sized array in struct/union
d:\a\jdk\jdk\src\hotspot\share\cds\cppVtables.cpp(69): note: This member will be ignored by a defaulted constructor or copy/move assignment operator

looks the the MSVC compiler is 'thinking' the same I was thinking :-) .
Should we better add an attribute to the function (maybe also a comment) ?

@xmas92
Copy link
Member

xmas92 commented Jun 10, 2024

I thought flexible array members were a C only thing.

I did something along the lines of this when I was experimenting with UBsan. Not sure if it is any better, but it does not use language extensions. Not sure if it is ok to look beyond the object through a intptr_t*. But at least it is not through a intptr_t[1].

diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp
index c339ce9c0de..55332dc484e 100644
--- a/src/hotspot/share/cds/cppVtables.cpp
+++ b/src/hotspot/share/cds/cppVtables.cpp
@@ -66,19 +66,19 @@
 
 class CppVtableInfo {
   intptr_t _vtable_size;
-  intptr_t _cloned_vtable[1];
+  intptr_t _cloned_vtable;
 public:
   static int num_slots(int vtable_size) {
     return 1 + vtable_size; // Need to add the space occupied by _vtable_size;
   }
   int vtable_size()           { return int(uintx(_vtable_size)); }
   void set_vtable_size(int n) { _vtable_size = intptr_t(n); }
-  intptr_t* cloned_vtable()   { return &_cloned_vtable[0]; }
-  void zero()                 { memset(_cloned_vtable, 0, sizeof(intptr_t) * vtable_size()); }
+  intptr_t* cloned_vtable()   { return &_cloned_vtable; }
+  void zero()                 { memset(&_cloned_vtable, 0, sizeof(intptr_t) * vtable_size()); }
   // Returns the address of the next CppVtableInfo that can be placed immediately after this CppVtableInfo
   static size_t byte_size(int vtable_size) {
     CppVtableInfo i;
-    return pointer_delta(&i._cloned_vtable[vtable_size], &i, sizeof(u1));
+    return pointer_delta(&i.cloned_vtable()[vtable_size], &i, sizeof(u1));
   }
 };
 

@TheRealMDoerr
Copy link
Contributor Author

Ah, flexible array members are C99, but not C++. GCC supports it, but obviously not your MSVC.

@TheRealMDoerr
Copy link
Contributor Author

@xmas92: Thanks! I have implemented a similar emulation for "flexible array members". Not sure which one is better.

@xmas92
Copy link
Member

xmas92 commented Jun 10, 2024

@xmas92: Thanks! I have implemented a similar emulation for "flexible array members". Not sure which one is better.

I like yours because it does not look beyond the object through a pointer into the object. It instead creates a pointer beyond the object and uses that. As an attached storage. Just like always, need to be careful with alignment and padding when adding storage beyond the objects representation. But everything is intptr_t here so it should all good.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jun 10, 2024
@TheRealMDoerr
Copy link
Contributor Author

Thanks for your review!

@MBaesken
Copy link
Member

Hi Martin, ubsan (on my Linux x86_64 test machine) is still happy with your latest patch.

@kimbarrett
Copy link

There are a number of "fake" VLA usage in HotSpot. Some of them have come up in recent ubsan cleanups for similar
reasons as here. There's a pattern that has been used in at least some of those places. See the class BufferNode in
share/gc/shared/bufferNode.hpp, for example. It would be nice to only have one pattern for this "feature".

@TheRealMDoerr
Copy link
Contributor Author

@kimbarrett: Thanks for taking a look! It makes sense to unify all VLA emulations. The implementation in BufferNode looks wrong, too. I believe specifying a length of 1 and accessing beyond it implies UB. Should I change that, too?

@kimbarrett
Copy link

@kimbarrett: Thanks for taking a look! It makes sense to unify all VLA emulations. The implementation in BufferNode looks wrong, too. I believe specifying a length of 1 and accessing beyond it implies UB. Should I change that, too?

I think it's not UB. Or if it is, then I don't see how the mechanism used in the current version of this change isn't
as well. The rationale for that belief is somewhat involved, and I think was discussed elsewhere. I'll see if I can find
or reconstruct it. Part of it is that the single-element _buffer is only used to provide the appropriately aligned
offset, with no accesses through that member.

@TheRealMDoerr
Copy link
Contributor Author

The implementation in BufferNode looks like the pattern which I'm fixing, here. The length is specified, but we're accessing the array beyond it and this is what UBSan is complaining about. The idea here is to avoid specifying the length and using a plain address computation instead. UBSan seems to be happy with it. Do you still see UB involved, here?

@kimbarrett
Copy link

The implementation in BufferNode looks like the pattern which I'm fixing, here. The length is specified, but we're accessing the array beyond it and this is what UBSan is complaining about. The idea here is to avoid specifying the length and using a plain address computation instead. UBSan seems to be happy with it. Do you still see UB involved, here?

They are different.

  • The current code takes the address of the array member and uses that as the
    base of an array access. So it's effectively doing obj._buffer[i] for i > 0.
    And that is UB, and ubsan rightly complains.

  • The BufferNode approach uses the array member to get the compiler-calculated
    offset from the object base to the start of the pseudo-VLA. It then does an
    address calculation with the containing object and that offset to get the
    address of the start of the buffer.

This approach was recently used to fix an identical ubsan issue:
https://bugs.openjdk.org/browse/JDK-8332683
G1: G1CardSetArray::EntryDataType [2] triggers ubsan runtime errors

  • The approach currently in the PR does some fragile ad hoc calculation to get
    the address of the start of the buffer. There's also no indication that we're
    simulating a VLA here.

@TheRealMDoerr
Copy link
Contributor Author

TheRealMDoerr commented Jun 14, 2024

They are different.

  • The current code takes the address of the array member and uses that as the
    base of an array access. So it's effectively doing obj._buffer[i] for i > 0.
    And that is UB, and ubsan rightly complains.
  • The BufferNode approach uses the array member to get the compiler-calculated
    offset from the object base to the start of the pseudo-VLA. It then does an
    address calculation with the containing object and that offset to get the
    address of the start of the buffer.

Got it, thanks! I had missed that BufferNode uses an address computation instead of array member access. So, it's basically the same trick as I was using.

This approach was recently used to fix an identical ubsan issue: https://bugs.openjdk.org/browse/JDK-8332683 G1: G1CardSetArray::EntryDataType [2] triggers ubsan runtime errors

  • The approach currently in the PR does some fragile ad hoc calculation to get
    the address of the start of the buffer. There's also no indication that we're
    simulating a VLA here.

I've updated my implementation to use _cloned_vtable[1] again with an address computation. Please note that we're calling it "Pseudo flexible array member", not VLA. I had also described it as "flexible array member" emulation in my previous version.

void zero() { memset(_cloned_vtable, 0, sizeof(intptr_t) * vtable_size()); }
// Using _cloned_vtable[i] for i > 0 causes undefined behavior. We use address calculation instead.
intptr_t* cloned_vtable() { return (intptr_t*)((char*)this + offset_of(CppVtableInfo, _cloned_vtable)); }
void zero() { memset(cloned_vtable(), 0, sizeof(intptr_t) * vtable_size()); }
// Returns the address of the next CppVtableInfo that can be placed immediately after this CppVtableInfo

Choose a reason for hiding this comment

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

The description of this function is wrong, as it returns an offset rather than
an address.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It returns a pointer which is computed by base + offset. I've factored out the offset computation.

Choose a reason for hiding this comment

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

The comment says byte_size() returns an address, but it actually returns a size_t offset.

// Returns the address of the next CppVtableInfo that can be placed immediately after this CppVtableInfo
static size_t byte_size(int vtable_size) {
CppVtableInfo i;
return pointer_delta(&i._cloned_vtable[vtable_size], &i, sizeof(u1));
return pointer_delta(&i.cloned_vtable()[vtable_size], &i, sizeof(u1));

Choose a reason for hiding this comment

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

Rather than making a dummy CppVTableInfo and doing pointer arithmetic, better
would be something like

offset_of(CppVtableInfo, _cloned_vtable) + (sizeof(intptr_t) * vtable_size)

It might be that some of the subexpressions of that should be broken out into helper
functions that can also be used in clone_vtable() and zero().

Also, the really paranoid might align_up that to alignof(CppVtableInfo). Currently that's a nop. Up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. The pointer_delta variant was not so nice.

@kimbarrett
Copy link

I've updated my implementation to use _cloned_vtable[1] again with an address computation. Please note that we're calling it "Pseudo flexible array member", not VLA. I had also described it as "flexible array member" emulation in my previous version.

gcc doc calls these VLAs, but C99 calls them FAM. FAM is fine; I'm just used to the older VLA terminology.

Copy link

@kimbarrett kimbarrett left a comment

Choose a reason for hiding this comment

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

Some more nits or pre-existing issues.

@@ -66,19 +66,20 @@

class CppVtableInfo {
intptr_t _vtable_size;
intptr_t _cloned_vtable[1];
intptr_t _cloned_vtable[1]; // Pseudo flexible array member.
static size_t cloned_vtable_offs() { return offset_of(CppVtableInfo, _cloned_vtable); }

Choose a reason for hiding this comment

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

I'd really prefer spelling out "offset" rather than saving two characters with the "offs" abbreviation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I don't mind.

@@ -66,19 +66,20 @@

class CppVtableInfo {
intptr_t _vtable_size;
intptr_t _cloned_vtable[1];
intptr_t _cloned_vtable[1]; // Pseudo flexible array member.
static size_t cloned_vtable_offs() { return offset_of(CppVtableInfo, _cloned_vtable); }
public:
static int num_slots(int vtable_size) {
return 1 + vtable_size; // Need to add the space occupied by _vtable_size;
}
int vtable_size() { return int(uintx(_vtable_size)); }

Choose a reason for hiding this comment

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

There's a bunch of pre-existing weirdness around the type of _vtable_size. (I think every use involves a
conversion.) Doing anything about that doesn't really belong in this change, but consider a followup cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Please note that I usually don't touch code in this area. If you would like it to get improved, I suggest filing an RFE and discussing with the CDS folks. My intention is to get rid of UB which is terrible.

@@ -66,19 +66,20 @@

class CppVtableInfo {
intptr_t _vtable_size;
intptr_t _cloned_vtable[1];
intptr_t _cloned_vtable[1]; // Pseudo flexible array member.
static size_t cloned_vtable_offs() { return offset_of(CppVtableInfo, _cloned_vtable); }
public:
static int num_slots(int vtable_size) {
return 1 + vtable_size; // Need to add the space occupied by _vtable_size;

Choose a reason for hiding this comment

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

Pre-existing: Maybe this ought to be byte_size() / sizeof(intptr_t) or something like that? And the
name num_slots seems confusing for what this is doing. Do we actually need both byte_size
and num_slots?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

num_slots is unused. Removed.

@MBaesken
Copy link
Member

MBaesken commented Jun 17, 2024

Hi Martin, thanks for fixing the issue (btw. I tested with the commit from this morning, ubsan was still 'happy').

Copy link

@kimbarrett kimbarrett left a comment

Choose a reason for hiding this comment

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

Looks good, other than pre-existing issues like the description of byte_size.
Those can be addressed later.

Copy link
Member

@xmas92 xmas92 left a comment

Choose a reason for hiding this comment

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

This approach seems alright to me.

However I am not sure I understood what was the problem with the initial solution where you make the metadata and the payload disjoint. Nor why the following statement no longer applies.

The current code takes the address of the array member and uses that as the
base of an array access. So it's effectively doing obj._buffer[i] for i > 0.
And that is UB, and ubsan rightly complains.

All of these implementations are now reinterpreting a field of type T[1] as a T[]. It is unclear to me why using offset_of changes anything.

Side note:
The thing I liked with the disjoin approach is that you create two objects, one with the metadata (the length in this case) and one which is the payload allocated with and created next to the metadata. As a form of attached storage. Using something like this:

T* payload() {  return reinterpret_cast<T*>(align_up(reinterpret_cast<char*>(this + 1), align_of(T))); }

Which for CppVtableInfo boiled down to &_vtable_size + 1.

It would be nicer to have a more explicit lifetime for these, such that you allocated the memory, get the char* metadata and char* payload addresses and then create the objects new (metadata) MetadataT(...) followed by new (payload) PayloadT(...).

@TheRealMDoerr
Copy link
Contributor Author

Thanks for the reviews and all comments! I also like your side note @xmas92. But, let's ship it.
/integrate

@openjdk
Copy link

openjdk bot commented Jun 18, 2024

Going to push as commit 0199fee.
Since your change was applied there have been 83 commits pushed to the master branch:

  • e95f092: 8333964: RISC-V: C2: Check "requires_strict_order" flag for floating-point add reduction
  • ba5a467: 8332854: Unable to build openjdk with --with-harfbuzz=system
  • 801bf15: 8332105: Exploded JDK does not include CDS
  • c94af6f: 8333962: Obsolete OldSize
  • cdf22b1: 8326715: ZGC: RunThese24H fails with ExitCode 139 during shutdown
  • ef7923e: 8334078: RISC-V: TestIntVect.java fails after JDK-8332153 when running without RVV
  • 0d1080d: 8331117: [PPC64] secondary_super_cache does not scale well
  • 113a2c0: 8332903: ubsan: opto/output.cpp:1002:18: runtime error: load of value 171, which is not a valid value for type 'bool'
  • d751441: 8330586: GHA: Drop additional gcc/glibc packages installation for x86_32
  • 5e09397: 8334222: exclude containers/cgroup/PlainRead.java
  • ... and 73 more: https://git.openjdk.org/jdk/compare/7b43a8cd7c663facbe490f889838d7ead0eba0f9...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jun 18, 2024
@openjdk openjdk bot closed this Jun 18, 2024
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jun 18, 2024
@openjdk
Copy link

openjdk bot commented Jun 18, 2024

@TheRealMDoerr Pushed as commit 0199fee.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@TheRealMDoerr TheRealMDoerr deleted the 8333639_UBSan_cppVtables branch June 18, 2024 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hotspot-runtime hotspot-runtime-dev@openjdk.org integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

4 participants