Skip to content

Commit 7926744

Browse files
committed
-fsanitize=alignment: check memcpy/memmove arguments (#67766)
The -fsanitize=alignment implementation follows the model that we allow forming unaligned pointers but disallow accessing unaligned pointers. See [RFC: Enforcing pointer type alignment in Clang](https://lists.llvm.org/pipermail/llvm-dev/2016-January/094012.html) for detail. memcpy is a memory access and we require an `int *` argument to be aligned. Similar to https://reviews.llvm.org/D9673 , emit -fsanitize=alignment check for arguments of builtin memcpy and memmove functions to catch misaligned load like: ``` // Check the alignment of a but ignore the alignment of b void unaligned_load(int *a, void *b) { memcpy(a, b, sizeof(*a)); } ``` For a reference parameter, we emit a -fsanitize=alignment check as well, which can be optimized out by InstCombinePass. We rely on the call site `TCK_ReferenceBinding` check instead. ``` // The alignment check of a will be optimized out. void unaligned_load(int &a, void *b) { memcpy(&a, b, sizeof(a)); } ``` The diagnostic message looks like ``` runtime error: store to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int *' ``` We could use a better message for memcpy, but we don't do it for now as it would require a new check name like misaligned-pointer-use, which is probably not necessary. *RFC: Enforcing pointer type alignment in Clang* is not well documented, but this patch does not intend to change the that. Technically builtin memset functions can be checked for -fsanitize=alignment as well, but it does not seem too useful.
1 parent 4790578 commit 7926744

File tree

4 files changed

+127
-14
lines changed

4 files changed

+127
-14
lines changed

clang/include/clang/Basic/Sanitizers.h

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ struct SanitizerSet {
170170
Mask = Value ? (Mask | K) : (Mask & ~K);
171171
}
172172

173+
void set(SanitizerMask K) { Mask = K; }
174+
173175
/// Disable the sanitizers specified in \p K.
174176
void clear(SanitizerMask K = SanitizerKind::All) { Mask &= ~K; }
175177

clang/lib/CodeGen/CGBuiltin.cpp

+27-12
Original file line numberDiff line numberDiff line change
@@ -2730,6 +2730,27 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
27302730
}
27312731
}
27322732

2733+
// Check NonnullAttribute/NullabilityArg and Alignment.
2734+
auto EmitArgCheck = [&](TypeCheckKind Kind, Address A, const Expr *Arg,
2735+
unsigned ParmNum) {
2736+
Value *Val = A.getPointer();
2737+
EmitNonNullArgCheck(RValue::get(Val), Arg->getType(), Arg->getExprLoc(), FD,
2738+
ParmNum);
2739+
2740+
if (SanOpts.has(SanitizerKind::Alignment)) {
2741+
SanitizerSet SkippedChecks;
2742+
SkippedChecks.set(SanitizerKind::All);
2743+
SkippedChecks.clear(SanitizerKind::Alignment);
2744+
SourceLocation Loc = Arg->getExprLoc();
2745+
// Strip an implicit cast.
2746+
if (auto *CE = dyn_cast<ImplicitCastExpr>(Arg))
2747+
if (CE->getCastKind() == CK_BitCast)
2748+
Arg = CE->getSubExpr();
2749+
EmitTypeCheck(Kind, Loc, Val, Arg->getType(), A.getAlignment(),
2750+
SkippedChecks);
2751+
}
2752+
};
2753+
27332754
switch (BuiltinIDIfNoAsmLabel) {
27342755
default: break;
27352756
case Builtin::BI__builtin___CFStringMakeConstantString:
@@ -3720,10 +3741,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
37203741
Address Dest = EmitPointerWithAlignment(E->getArg(0));
37213742
Address Src = EmitPointerWithAlignment(E->getArg(1));
37223743
Value *SizeVal = EmitScalarExpr(E->getArg(2));
3723-
EmitNonNullArgCheck(RValue::get(Dest.getPointer()), E->getArg(0)->getType(),
3724-
E->getArg(0)->getExprLoc(), FD, 0);
3725-
EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(),
3726-
E->getArg(1)->getExprLoc(), FD, 1);
3744+
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
3745+
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
37273746
Builder.CreateMemCpy(Dest, Src, SizeVal, false);
37283747
if (BuiltinID == Builtin::BImempcpy ||
37293748
BuiltinID == Builtin::BI__builtin_mempcpy)
@@ -3738,10 +3757,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
37383757
Address Src = EmitPointerWithAlignment(E->getArg(1));
37393758
uint64_t Size =
37403759
E->getArg(2)->EvaluateKnownConstInt(getContext()).getZExtValue();
3741-
EmitNonNullArgCheck(RValue::get(Dest.getPointer()), E->getArg(0)->getType(),
3742-
E->getArg(0)->getExprLoc(), FD, 0);
3743-
EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(),
3744-
E->getArg(1)->getExprLoc(), FD, 1);
3760+
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
3761+
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
37453762
Builder.CreateMemCpyInline(Dest, Src, Size);
37463763
return RValue::get(nullptr);
37473764
}
@@ -3798,10 +3815,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
37983815
Address Dest = EmitPointerWithAlignment(E->getArg(0));
37993816
Address Src = EmitPointerWithAlignment(E->getArg(1));
38003817
Value *SizeVal = EmitScalarExpr(E->getArg(2));
3801-
EmitNonNullArgCheck(RValue::get(Dest.getPointer()), E->getArg(0)->getType(),
3802-
E->getArg(0)->getExprLoc(), FD, 0);
3803-
EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(),
3804-
E->getArg(1)->getExprLoc(), FD, 1);
3818+
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
3819+
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
38053820
Builder.CreateMemMove(Dest, Src, SizeVal, false);
38063821
return RValue::get(Dest.getPointer());
38073822
}

clang/test/CodeGen/catch-undef-behavior.c

+75-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
// CHECK-UBSAN: @[[SCHAR:.*]] = private unnamed_addr constant { i16, i16, [14 x i8] } { i16 0, i16 7, [14 x i8] c"'signed char'\00" }
2828
// CHECK-UBSAN: @[[LINE_1500:.*]] = {{.*}}, i32 1500, i32 10 {{.*}} @[[FP16]], {{.*}} }
2929

30+
// CHECK-UBSAN: @[[PLONG:.*]] = private unnamed_addr constant { i16, i16, [9 x i8] } { i16 -1, i16 0, [9 x i8] c"'long *'\00" }
31+
// CHECK-UBSAN: @[[LINE_1600:.*]] = {{.*}}, i32 1600, i32 10 {{.*}} @[[PLONG]], {{.*}} }
32+
3033
// PR6805
3134
// CHECK-COMMON-LABEL: @foo
3235
void foo(void) {
@@ -354,21 +357,69 @@ void call_decl_nonnull(int *a) {
354357
decl_nonnull(a);
355358
}
356359

357-
extern void *memcpy (void *, const void *, unsigned) __attribute__((nonnull(1, 2)));
360+
extern void *memcpy(void *, const void *, unsigned long) __attribute__((nonnull(1, 2)));
358361

359362
// CHECK-COMMON-LABEL: @call_memcpy_nonnull
360363
void call_memcpy_nonnull(void *p, void *q, int sz) {
361364
// CHECK-COMMON: icmp ne ptr {{.*}}, null
362365
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg
363366
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
367+
// CHECK-COMMON-NOT: call
364368

365369
// CHECK-COMMON: icmp ne ptr {{.*}}, null
366370
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg
367371
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
372+
// CHECK-COMMON-NOT: call
373+
374+
// CHECK-COMMON: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %0, ptr align 1 %1, i64 %conv, i1 false)
375+
memcpy(p, q, sz);
376+
}
377+
378+
// CHECK-COMMON-LABEL: define{{.*}} void @call_memcpy(
379+
void call_memcpy(long *p, short *q, int sz) {
380+
// CHECK-COMMON: icmp ne ptr {{.*}}, null
381+
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg(
382+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
383+
// CHECK-COMMON: and i64 %[[#]], 7, !nosanitize
384+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
385+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(ptr @[[LINE_1600]]
386+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
387+
388+
// CHECK-COMMON: icmp ne ptr {{.*}}, null
389+
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg(
390+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
391+
// CHECK-COMMON: and i64 %[[#]], 1, !nosanitize
392+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
393+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(
394+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
395+
396+
// CHECK-COMMON: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %0, ptr align 2 %1, i64 %conv, i1 false)
397+
398+
// CHECK-UBSAN-NOT: call void @__ubsan_handle_type_mismatch_v1(
399+
// CHECK-COMMON: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %[[#]], ptr align 1 %[[#]], i64 %{{.*}}, i1 false)
400+
#line 1600
368401
memcpy(p, q, sz);
402+
/// Casting to void * or char * drops the alignment requirement.
403+
memcpy((void *)p, (char *)q, sz);
369404
}
370405

371-
extern void *memmove (void *, const void *, unsigned) __attribute__((nonnull(1, 2)));
406+
// CHECK-COMMON-LABEL: define{{.*}} void @call_memcpy_inline(
407+
void call_memcpy_inline(long *p, short *q) {
408+
// CHECK-COMMON: and i64 %[[#]], 7, !nosanitize
409+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
410+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(
411+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
412+
413+
// CHECK-COMMON: and i64 %[[#]], 1, !nosanitize
414+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
415+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(
416+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
417+
418+
// CHECK-COMMON: call void @llvm.memcpy.inline.p0.p0.i64(ptr align 8 %0, ptr align 2 %1, i64 2, i1 false)
419+
__builtin_memcpy_inline(p, q, 2);
420+
}
421+
422+
extern void *memmove(void *, const void *, unsigned long) __attribute__((nonnull(1, 2)));
372423

373424
// CHECK-COMMON-LABEL: @call_memmove_nonnull
374425
void call_memmove_nonnull(void *p, void *q, int sz) {
@@ -382,6 +433,28 @@ void call_memmove_nonnull(void *p, void *q, int sz) {
382433
memmove(p, q, sz);
383434
}
384435

436+
// CHECK-COMMON-LABEL: define{{.*}} void @call_memmove(
437+
void call_memmove(long *p, short *q, int sz) {
438+
// CHECK-COMMON: icmp ne ptr {{.*}}, null
439+
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg(
440+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
441+
// CHECK-COMMON: and i64 %[[#]], 7, !nosanitize
442+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
443+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(
444+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
445+
446+
// CHECK-COMMON: icmp ne ptr {{.*}}, null
447+
// CHECK-UBSAN: call void @__ubsan_handle_nonnull_arg(
448+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 16)
449+
// CHECK-COMMON: and i64 %[[#]], 1, !nosanitize
450+
// CHECK-COMMON: icmp eq i64 %[[#]], 0, !nosanitize
451+
// CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1(
452+
// CHECK-TRAP: call void @llvm.ubsantrap(i8 22)
453+
454+
// CHECK-COMMON: call void @llvm.memmove.p0.p0.i64(ptr align 8 %0, ptr align 2 %1, i64 %conv, i1 false)
455+
memmove(p, q, sz);
456+
}
457+
385458
// CHECK-COMMON-LABEL: @call_nonnull_variadic
386459
__attribute__((nonnull)) void nonnull_variadic(int a, ...);
387460
void call_nonnull_variadic(int a, int *b) {

compiler-rt/test/ubsan/TestCases/TypeCheck/misaligned.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// RUN: %clangxx %gmlt -fsanitize=alignment %s -O3 -o %t
22
// RUN: %run %t l0 && %run %t s0 && %run %t r0 && %run %t m0 && %run %t f0 && %run %t n0 && %run %t u0
33
// RUN: %run %t l1 2>&1 | FileCheck %s --check-prefix=CHECK-LOAD --strict-whitespace
4+
// RUN: %run %t L1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMCPY-LOAD
5+
// RUN: %run %t S1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMCPY-STORE
46
// RUN: %run %t r1 2>&1 | FileCheck %s --check-prefix=CHECK-REFERENCE
57
// RUN: %run %t m1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMBER
68
// RUN: %run %t f1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMFUN
@@ -15,6 +17,7 @@
1517
// XFAIL: target={{.*openbsd.*}}
1618

1719
#include <new>
20+
#include <string.h>
1821

1922
struct S {
2023
S() {}
@@ -47,6 +50,16 @@ int main(int, char **argv) {
4750
return *p && 0;
4851
// CHECK-STACK-LOAD: #0 {{.*}}main{{.*}}misaligned.cpp
4952

53+
case 'L': {
54+
int x;
55+
// CHECK-MEMCPY-LOAD: misaligned.cpp:[[#@LINE+4]]{{(:16)?}}: runtime error: load of misaligned address [[PTR:0x[0-9a-f]*]] for type 'int *', which requires 4 byte alignment
56+
// CHECK-MEMCPY-LOAD-NEXT: [[PTR]]: note: pointer points here
57+
// CHECK-MEMCPY-LOAD-NEXT: {{^ 00 00 00 01 02 03 04 05}}
58+
// CHECK-MEMCPY-LOAD-NEXT: {{^ \^}}
59+
memcpy(&x, p, sizeof(x));
60+
return x && 0;
61+
}
62+
5063
case 's':
5164
// CHECK-STORE: misaligned.cpp:[[@LINE+4]]{{(:5)?}}: runtime error: store to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int', which requires 4 byte alignment
5265
// CHECK-STORE-NEXT: [[PTR]]: note: pointer points here
@@ -55,6 +68,16 @@ int main(int, char **argv) {
5568
*p = 1;
5669
break;
5770

71+
case 'S': {
72+
int x = 1;
73+
// CHECK-MEMCPY-STORE: misaligned.cpp:[[#@LINE+4]]{{(:12)?}}: runtime error: store to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int *', which requires 4 byte alignment
74+
// CHECK-MEMCPY-STORE-NEXT: [[PTR]]: note: pointer points here
75+
// CHECK-MEMCPY-STORE-NEXT: {{^ 00 00 00 01 02 03 04 05}}
76+
// CHECK-MEMCPY-STORE-NEXT: {{^ \^}}
77+
memcpy(p, &x, sizeof(x));
78+
break;
79+
}
80+
5881
case 'r':
5982
// CHECK-REFERENCE: misaligned.cpp:[[@LINE+4]]{{(:(5|15))?}}: runtime error: reference binding to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int', which requires 4 byte alignment
6083
// CHECK-REFERENCE-NEXT: [[PTR]]: note: pointer points here

0 commit comments

Comments
 (0)