This is an archive of the discontinued LLVM Phabricator instance.

[instcombine] Exploit UB implied by nofree attributes
ClosedPublic

Authored by reames on Feb 9 2021, 9:08 AM.

Details

Summary

This patch simply implements the documented UB of the current nofree attributes as specified. It doesn't try to be fancy about inference (yet), it just implements the cases already specified and inferred.

Note: When this lands, it may expose miscompiles. If so, please revert and provide a test case. It's likely the bug is in the existing inference code and without a relatively complete test case, it will be hard to debug.

Diff Detail

Event Timeline

reames created this revision.Feb 9 2021, 9:08 AM
reames requested review of this revision.Feb 9 2021, 9:08 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 9 2021, 9:08 AM
jdoerfert accepted this revision.Feb 9 2021, 11:07 AM

LGTM. We should probably add the following negative test case:

; Freeing in a nonfree function is fine if the effect is invisible to the outside
define void @test16() nofree {
  i8* %foo = call i8* @malloc(i32 1)
  call void @free(i8* %foo)
  ret void
}
This revision is now accepted and ready to land.Feb 9 2021, 11:07 AM
reames added a comment.Feb 9 2021, 2:40 PM

LGTM. We should probably add the following negative test case:

; Freeing in a nonfree function is fine if the effect is invisible to the outside
define void @test16() nofree {
  i8* %foo = call i8* @malloc(i32 1)
  call void @free(i8* %foo)
  ret void
}

Er, no. That's not okay. Either by the implementation in this patch, or by my reading of the LangRef. The langref says "This function attribute indicates that the function does not, directly or indirectly, call a memory-deallocation function (free, for example). " There's no exception there for memory allocated in scope, nor should there be.

LGTM. We should probably add the following negative test case:

; Freeing in a nonfree function is fine if the effect is invisible to the outside
define void @test16() nofree {
  i8* %foo = call i8* @malloc(i32 1)
  call void @free(i8* %foo)
  ret void
}

Er, no. That's not okay. Either by the implementation in this patch, or by my reading of the LangRef. The langref says "This function attribute indicates that the function does not, directly or indirectly, call a memory-deallocation function (free, for example). " There's no exception there for memory allocated in scope, nor should there be.

Right, we want to tweak the lang ref. Generally, I believe this is is one of those cases where it is "as if" it doesn't free. I mean, you can't tell it does from the outside. Imagine a backend that realizes the stack allocation is too big and puts it on the heap with a proper free. If the function was nofree before it would not be anymore even though we might have "thought so" during the compilation. Similarly, a writenone function can write to some alloca without it being a problem (IMHO).

reames added a comment.Feb 9 2021, 3:56 PM

LGTM. We should probably add the following negative test case:

; Freeing in a nonfree function is fine if the effect is invisible to the outside
define void @test16() nofree {
  i8* %foo = call i8* @malloc(i32 1)
  call void @free(i8* %foo)
  ret void
}

Er, no. That's not okay. Either by the implementation in this patch, or by my reading of the LangRef. The langref says "This function attribute indicates that the function does not, directly or indirectly, call a memory-deallocation function (free, for example). " There's no exception there for memory allocated in scope, nor should there be.

Right, we want to tweak the lang ref.

I'm not going to enter a debate on whether the LangRef is right here. We can do that on llvm-dev or private conversation. This patch is solely implementing the semantics as currently specified.

Does your LGTM still stand with that understanding?

LGTM. We should probably add the following negative test case:

; Freeing in a nonfree function is fine if the effect is invisible to the outside
define void @test16() nofree {
  i8* %foo = call i8* @malloc(i32 1)
  call void @free(i8* %foo)
  ret void
}

Er, no. That's not okay. Either by the implementation in this patch, or by my reading of the LangRef. The langref says "This function attribute indicates that the function does not, directly or indirectly, call a memory-deallocation function (free, for example). " There's no exception there for memory allocated in scope, nor should there be.

Right, we want to tweak the lang ref.

I'm not going to enter a debate on whether the LangRef is right here. We can do that on llvm-dev or private conversation. This patch is solely implementing the semantics as currently specified.

Does your LGTM still stand with that understanding?

Yes, this is restrictive enough to be correct either way.

This revision was landed with ongoing or failed builds.Feb 18 2021, 8:34 AM
This revision was automatically updated to reflect the committed changes.

FYI, there was a nasty bug found in this patch. A fix is on review in https://reviews.llvm.org/D100779.