This is an archive of the discontinued LLVM Phabricator instance.

[DSE] Use precise loc for memset_chk during overwrite checks
ClosedPublic

Authored by fhahn on Dec 6 2021, 9:52 AM.

Details

Summary

memset_chk may not write the number of bytes specified by the third
argument, if it is larger than the destination size (specified as 4th
argument).

Diff Detail

Event Timeline

fhahn created this revision.Dec 6 2021, 9:52 AM
fhahn requested review of this revision.Dec 6 2021, 9:52 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 6 2021, 9:52 AM

A couple thoughts:

  1. We need to be careful using the exact memset size. We could end up with an "impossible" memory location: for example, setting 100 bytes of a 50-byte buffer. That's probably okay here? But maybe not in other contexts; we need to be sure we don't treat memset_chk as UB. Maybe worth a comment describing what, exactly, we're modeling.
  2. In the case where the bounds check in memset_chk fails, it just aborts the process, and there isn't any synchronization point before it does that. So I'm not sure why it matters if the pointer is visible to other threads. Another thread can't read the contents before the process abort because it would be unsynchronized, and the memory doesn't exist after the process aborts.
fhahn added a comment.Dec 7 2021, 8:43 AM

A couple thoughts:

Thanks for taking a look!

  1. We need to be careful using the exact memset size. We could end up with an "impossible" memory location: for example, setting 100 bytes of a 50-byte buffer. That's probably okay here? But maybe not in other contexts; we need to be sure we don't treat memset_chk as UB. Maybe worth a comment describing what, exactly, we're modeling.

I think using an 'impossible' memory location should be fine here, we use the location to remove stores based on the fact that the memset_chk is executed and impossible locations will cause an abort. It's a bit of a shame that after all the work of moving most location reasoning from DSE to MemoryLocation we still need special logic here that doesn't fit into MemoryLocation. But as you said, it is not safe in general.

  1. In the case where the bounds check in memset_chk fails, it just aborts the process, and there isn't any synchronization point before it does that. So I'm not sure why it matters if the pointer is visible to other threads. Another thread can't read the contents before the process abort because it would be unsynchronized, and the memory doesn't exist after the process aborts.

It might have been overly conservative on my side. I was thinking about cases where a failing memset_chk aborts the current thread/process but not other threads or processes that may access the same (shared) memory. But if that's not an issue the restriction can be removed.

It might have been overly conservative on my side. I was thinking about cases where a failing memset_chk aborts the current thread/process but not other threads or processes that may access the same (shared) memory. But if that's not an issue the restriction can be removed.

memset_chk aborts the whole process. Throwing an exception on an out-of-bounds access is a popular model in other contexts, but memset_chk is not defined that way. It's meant purely as a hardening measure.

memset_chk should not be used on memory shared with other processes. Accesses to such memory should be volatile or atomic; otherwise, weird stuff can happen for a variety of reasons...

fhahn updated this revision to Diff 392782.Dec 8 2021, 7:53 AM

It might have been overly conservative on my side. I was thinking about cases where a failing memset_chk aborts the current thread/process but not other threads or processes that may access the same (shared) memory. But if that's not an issue the restriction can be removed.

memset_chk aborts the whole process. Throwing an exception on an out-of-bounds access is a popular model in other contexts, but memset_chk is not defined that way. It's meant purely as a hardening measure.

memset_chk should not be used on memory shared with other processes. Accesses to such memory should be volatile or atomic; otherwise, weird stuff can happen for a variety of reasons...

Sounds good, thanks! I remove the local-object restriction and added a comment explaining why using the precise bound is fine here.

nikic added inline comments.Dec 8 2021, 9:42 AM
llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
1025 ↗(On Diff #392782)

No longer necessary.

1041 ↗(On Diff #392782)

This hold for AA results in general though (they are only valid under the assumption of execution), so maybe it's okay to return a precise result directly in MemoryLocation?

fhahn marked an inline comment as done.Dec 8 2021, 11:56 AM
fhahn added inline comments.
llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
1041 ↗(On Diff #392782)

Hm, I am not sure. For AA it should be fine for the same reasons it's fine for DSE, but I am not sure about some of the other users? Grepping surfaces a couple of additional ones.

nikic added inline comments.Dec 13 2021, 11:44 AM
llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
1041 ↗(On Diff #392782)

Looking through other MemoryLocation::getForDest() users, all of them seem to be limited to handling intrinsics, not libcalls, so I don't expect issues with existing callers.

fhahn updated this revision to Diff 394287.Dec 14 2021, 9:24 AM
fhahn marked an inline comment as done.

Move logic to MemoryLocation, add comment about correctness limited to contexts where the call executes.

fhahn added inline comments.Dec 14 2021, 9:29 AM
llvm/lib/Analysis/MemoryLocation.cpp
256

I now moved the code, but I'm not sure how to adjust/frame the comment here.

nikic added inline comments.Dec 15 2021, 5:23 AM
llvm/lib/Analysis/MemoryLocation.cpp
256

memset_chk will either write exactly Len bytes or abort the process. If memset_chk aborts, then the partial write cannot be observed. For analysis purposes we can assume that exactly Len bytes are written.

nikic added inline comments.Dec 15 2021, 2:25 PM
llvm/lib/Analysis/MemoryLocation.cpp
256

Okay, I'm having second thoughts here, maybe we should go back to your previous version.

The main danger is that something decides that because the precise location is larger than the underlying object, the access must be UB. And indeed, this is exactly what we're doing in https://github.com/llvm/llvm-project/blob/15c8b8ad85c196df25aa3c22bca3d8458f86525a/llvm/lib/Analysis/BasicAliasAnalysis.cpp#L1604-L1613. My earlier statement that this would be fine for alias analysis was wrong.

Though I think that this may be problematic for DSE for the same reason. We pass the location to AA and it could determine that it thus doesn't alias. The saving grace here is probably that https://github.com/llvm/llvm-project/blob/3d595eccc7d5b20d9f202492bf48394ac7c078c6/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp#L926-L935 only uses the NoAlias result if the underlying objects differ, but one could probably still get an incorrect NoAlias result here if the underlying object is hidden in a way that BasicAA understands but DSE does not (e.g. through a phi or select).

fhahn updated this revision to Diff 477605.Nov 23 2022, 2:22 PM

Finally had some time to look at this again. I updated the patch to move the logic back to DSE and just use it for the sizes used to determine whether an object is overwritten. That should avoid any issues with AA queries.

Herald added a project: Restricted Project. · View Herald TranscriptNov 23 2022, 2:22 PM
fhahn marked an inline comment as done.Nov 23 2022, 2:23 PM
fhahn added inline comments.
llvm/lib/Analysis/MemoryLocation.cpp
256

Great point, thanks for checking and sorry for the long delay.

Finally had some time to look at this again. I updated the patch to move the logic back to DSE and just use it for the sizes used to determine whether an object is overwritten. That should avoid any issues with AA queries.

fhahn retitled this revision from [DSE] Use precise loc for memset_chk writing to local objects. to [DSE] Use precise loc for memset_chk during overwrite checks.Nov 23 2022, 2:37 PM
fhahn edited the summary of this revision. (Show Details)
asbirlea accepted this revision.Dec 1 2022, 9:50 AM

Considering the previous reviewer comments, this lgtm.

llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
886 ↗(On Diff #477605)

nit: remove 'during'

llvm/test/Transforms/DeadStoreElimination/libcalls.ll
481

nit: rename function without 'cannot'

This revision is now accepted and ready to land.Dec 1 2022, 9:50 AM
fhahn updated this revision to Diff 479414.Dec 1 2022, 1:42 PM
fhahn marked an inline comment as done.

Thanks, rebase after test update, I am planning to land this soon.

This revision was automatically updated to reflect the committed changes.
fhahn marked 2 inline comments as done.Dec 2 2022, 3:31 AM
fhahn added inline comments.
llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
886 ↗(On Diff #477605)

Thanks, removed in the committed version!

llvm/test/Transforms/DeadStoreElimination/libcalls.ll
481

I updated the tests separately, thanks!