-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[TySan] Fixed false positive when accessing offset member variables #95387
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
[TySan] Fixed false positive when accessing offset member variables #95387
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-compiler-rt-sanitizer Author: None (gbMattN) ChangesThis patch fixes a bug the current TySan implementation has. Currently if you access a member variable other than the first, TySan reports an error. TySan believes you are accessing the struct type with an offset equal to the offset of the member variable you are trying to access. Full diff: https://github.com/llvm/llvm-project/pull/95387.diff 2 Files Affected:
diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp
index f627851d049e6..747727e48a152 100644
--- a/compiler-rt/lib/tysan/tysan.cpp
+++ b/compiler-rt/lib/tysan/tysan.cpp
@@ -221,7 +221,17 @@ __tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) {
OldTDPtr -= i;
OldTD = *OldTDPtr;
- if (!isAliasingLegal(td, OldTD))
+ tysan_type_descriptor *InternalMember = OldTD;
+ if (OldTD->Tag == TYSAN_STRUCT_TD) {
+ for (int j = 0; j < OldTD->Struct.MemberCount; j++) {
+ if (OldTD->Struct.Members[j].Offset == i) {
+ InternalMember = OldTD->Struct.Members[j].Type;
+ break;
+ }
+ }
+ }
+
+ if (!isAliasingLegal(td, InternalMember))
reportError(addr, size, td, OldTD, AccessStr,
"accesses part of an existing object", -i, pc, bp, sp);
diff --git a/compiler-rt/test/tysan/struct-members.c b/compiler-rt/test/tysan/struct-members.c
new file mode 100644
index 0000000000000..8cf6499f78ce6
--- /dev/null
+++ b/compiler-rt/test/tysan/struct-members.c
@@ -0,0 +1,32 @@
+// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1
+// RUN: FileCheck %s < %t.out
+
+#include <stdio.h>
+
+struct X {
+ int a, b, c;
+} x;
+
+static struct X xArray[2];
+
+int main() {
+ x.a = 1;
+ x.b = 2;
+ x.c = 3;
+
+ printf("%d %d %d\n", x.a, x.b, x.c);
+ // CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation
+
+ for (size_t i = 0; i < 2; i++) {
+ xArray[i].a = 1;
+ xArray[i].b = 1;
+ xArray[i].c = 1;
+ }
+ printf("Here\n");
+
+ struct X *xPtr = (struct X *)&(xArray[0].c);
+ xPtr->a = 1;
+ // CHECK: ERROR: TypeSanitizer: type-aliasing-violation
+ // CHECK: WRITE of size 4 at {{.*}} with type int (in X at offset 0) accesses an existing object of type int (in X at offset 8)
+ // CHECK: {{#0 0x.* in main .*struct-members.c:}}[[@LINE-3]]
+}
|
f7e775f
to
432f994
Compare
This may be a side effect of a different bug tracking global variables. I think fixing that bug first, and then applying this change if the problem persists is a better idea. Because of this, I'm switching this to a draft for now. Discourse link is https://discourse.llvm.org/t/reviving-typesanitizer-a-sanitizer-to-catch-type-based-aliasing-violations/66092/23 |
compiler-rt/lib/tysan/tysan.cpp
Outdated
@@ -221,7 +221,17 @@ __tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) { | |||
OldTDPtr -= i; | |||
OldTD = *OldTDPtr; | |||
|
|||
if (!isAliasingLegal(td, OldTD)) | |||
tysan_type_descriptor *InternalMember = OldTD; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment here indicating what this does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! Let me know if I need to clarify anything in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like you should name the variable declared here "AccessedType" or similar, as the default-path is to check access to OldTD rather than an internal member of it, right?
432f994
to
8b9530d
Compare
e2caf4e
to
733b3ed
Compare
8b9530d
to
547444a
Compare
547444a
to
8099113
Compare
83a3688
to
8099113
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with TySan, but here's some style feedback and similar inline.
The comments in tysan.cpp are too long (clang-format-bot would have bitten you if it was live when you uploaded this), please clang-format.
compiler-rt/lib/tysan/tysan.cpp
Outdated
// Therefore, if you are accessing a struct, we need to find the member type. We can go through the | ||
// members of the struct type and see if there is a member at the offset you are accessing the struct by. | ||
// If there is indeed a member starting at offset 'i' in the struct, we should check aliasing legality | ||
// with that type. If there isn't, we run alias checking on the struct with will give us the correct error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// with that type. If there isn't, we run alias checking on the struct with will give us the correct error. | |
// with that type. If there isn't, we run alias checking on the struct which will give us the correct error. |
Doesn't parse without this change?
compiler-rt/lib/tysan/tysan.cpp
Outdated
// with that type. If there isn't, we run alias checking on the struct with will give us the correct error. | ||
tysan_type_descriptor *InternalMember = OldTD; | ||
if (OldTD->Tag == TYSAN_STRUCT_TD) { | ||
for (int j = 0; j < OldTD->Struct.MemberCount; j++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style guide says ++j
compiler-rt/lib/tysan/tysan.cpp
Outdated
@@ -221,7 +221,17 @@ __tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) { | |||
OldTDPtr -= i; | |||
OldTD = *OldTDPtr; | |||
|
|||
if (!isAliasingLegal(td, OldTD)) | |||
tysan_type_descriptor *InternalMember = OldTD; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like you should name the variable declared here "AccessedType" or similar, as the default-path is to check access to OldTD rather than an internal member of it, right?
x.c = 3; | ||
|
||
printf("%d %d %d\n", x.a, x.b, x.c); | ||
// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with the output of TySan, but this CHECK-NOT paired with the CHECK on line 28 is going to test that "there's no error until there's an error", which makes this specific CHECK-NOT redundant. If you're looking to ensure there's only one instance of that error-output then you need to put another CHECK-NOT under the CHECK lines below, or add --implicit-check-not
to FileCheck. Also consider just not having this CHECK-NOT line, if TySan will never produce more than one error.
// CHECK: ERROR: TypeSanitizer: type-aliasing-violation | ||
// CHECK: WRITE of size 4 at {{.*}} with type int (in X at offset 0) accesses an existing object of type int (in X at offset 8) | ||
// CHECK: {{#0 0x.* in main .*struct-members.c:}}[[@LINE-3]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to strengthen this test even further by making the latter two "CHECK-NEXT", to ensure the error appears all in one block? (Reduces the risk of spurious extra output matching CHECK lines).
I'd recommend reducing the portions of the CHECK-line that are regexes to be as small as possible, so {{.*}}struct-members.c
and similar.
733b3ed
to
8e6e62d
Compare
be4f4c5
to
9a2c70c
Compare
You can test this locally with the following command:git-clang-format --diff 8e6e62d0dee48a696afd0c7d53d74eaccef97b5e 8addd061964253a1100d76446569ff1f67e63a9c --extensions cpp,c -- compiler-rt/test/tysan/global-struct-members.c compiler-rt/lib/tysan/tysan.cpp View the diff from clang-format here.diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp
index 228a5f13f5..da92e64181 100644
--- a/compiler-rt/lib/tysan/tysan.cpp
+++ b/compiler-rt/lib/tysan/tysan.cpp
@@ -226,20 +226,21 @@ __tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) {
int i = -((sptr)OldTD);
OldTDPtr -= i;
OldTD = *OldTDPtr;
-
+
tysan_type_descriptor *AccessedType = OldTD;
-
+
// Only check if we are accessing members if the type exists
- if(OldTD != nullptr){
- // When shadow memory is set for global objects, the entire object is tagged
- // with the struct type This means that when you access a member variable,
- // tysan reads that as you accessing a struct midway through, with 'i' being
- // the offset Therefore, if you are accessing a struct, we need to find the
- // member type. We can go through the members of the struct type and see if
- // there is a member at the offset you are accessing the struct by. If there
- // is indeed a member starting at offset 'i' in the struct, we should check
- // aliasing legality with that type. If there isn't, we run alias checking
- // on the struct which will give us the correct error.
+ if (OldTD != nullptr) {
+ // When shadow memory is set for global objects, the entire object is
+ // tagged with the struct type This means that when you access a member
+ // variable, tysan reads that as you accessing a struct midway through,
+ // with 'i' being the offset Therefore, if you are accessing a struct, we
+ // need to find the member type. We can go through the members of the
+ // struct type and see if there is a member at the offset you are
+ // accessing the struct by. If there is indeed a member starting at offset
+ // 'i' in the struct, we should check aliasing legality with that type. If
+ // there isn't, we run alias checking on the struct which will give us the
+ // correct error.
if (OldTD->Tag == TYSAN_STRUCT_TD) {
for (int j = 0; j < OldTD->Struct.MemberCount; ++j) {
if (OldTD->Struct.Members[j].Offset == i) {
|
5c0e520
to
6f48a28
Compare
Sorry I didn't mean to close this PR, looks like it happened automatically once I deleted the |
This patch fixes a bug the current TySan implementation has. Currently if you access a member variable other than the first, TySan reports an error. TySan believes you are accessing the struct type with an offset equal to the offset of the member variable you are trying to access.
With this patch, the type we are trying to access is amended to the type of the member variable matching the offset we are accessing with. It does this if and only if there is a member at that offset, however, so any incorrect accesses are still caught. This is checked in the struct-members.c test.