Hi! This patch implements sanitization for nonnull and returns_nonnull attributes. Similarly to -fsanitize=null this option disables -fdelete-null-pointer-checks silently, and adds checks before calling functions with declared nonnull arguments if we don't pass NULL there, and before return in functions declared with returns_nonnull also inserts check whether the return value is not NULL.
As GCC 4.9.0+ now aggressively optimizes based on these attributes and we've seen several issues in real world apps, I think this is really needed. Bootstrapped/regtested on x86_64-linux and i686-linux normally (yes,rtl checking) and c,c++,fortran release checking bootstrap-ubsan. Will post bugs in gcc discovered by it later. The patch adds two new (trivial handlers) to libubsan, as it is maintained in llvm's compiler-rt, will talk to them if they are interested in those and what exact wording and form (AFAIK clang also added the gcc {,returns_}nonnull attributes). If they wouldn't be interested, guess we could add them in a separate, gcc owned, source file in ubsan (like we own Makefile*). 2014-06-27 Jakub Jelinek <ja...@redhat.com> * flag-types.h (enum sanitize_code): Add SANITIZE_NONNULL and SANITIZE_RETURNS_NONNULL, or them into SANITIZE_UNDEFINED. * opts.c (common_handle_option): Handle SANITIZE_NONNULL and SANITIZE_RETURNS_NONNULL and disable flag_delete_null_pointer_checks for them. * sanitizer.def (BUILT_IN_UBSAN_HANDLE_NONNULL_ARG, BUILT_IN_UBSAN_HANDLE_NONNULL_ARG_ABORT, BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN, BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN_ABORT): New. * ubsan.c (instrument_bool_enum_load): Set *gsi back to stmt's iterator. (instrument_nonnull_arg, instrument_nonnull_return): New functions. (pass_ubsan::gate): Return true even for SANITIZE_NONNULL or SANITIZE_RETURNS_NONNULL. (pass_ubsan::execute): Call instrument_nonnull_{arg,return}. * c-c++-common/ubsan/nonnull-1.c: New test. * c-c++-common/ubsan/nonnull-2.c: New test. * c-c++-common/ubsan/nonnull-3.c: New test. * c-c++-common/ubsan/nonnull-4.c: New test. * c-c++-common/ubsan/nonnull-5.c: New test. --- gcc/flag-types.h.jj 2014-06-20 23:26:24.000000000 +0200 +++ gcc/flag-types.h 2014-06-26 14:39:44.133970103 +0200 @@ -231,10 +231,13 @@ enum sanitize_code { SANITIZE_FLOAT_DIVIDE = 1 << 12, SANITIZE_FLOAT_CAST = 1 << 13, SANITIZE_BOUNDS = 1 << 14, + SANITIZE_NONNULL = 1 << 15, + SANITIZE_RETURNS_NONNULL = 1 << 16, SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN | SANITIZE_SI_OVERFLOW | SANITIZE_BOOL | SANITIZE_ENUM - | SANITIZE_BOUNDS, + | SANITIZE_BOUNDS | SANITIZE_NONNULL + | SANITIZE_RETURNS_NONNULL, SANITIZE_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST }; --- gcc/opts.c.jj 2014-06-20 23:26:23.000000000 +0200 +++ gcc/opts.c 2014-06-26 14:43:51.620652007 +0200 @@ -1468,6 +1468,9 @@ common_handle_option (struct gcc_options { "float-cast-overflow", SANITIZE_FLOAT_CAST, sizeof "float-cast-overflow" - 1 }, { "bounds", SANITIZE_BOUNDS, sizeof "bounds" - 1 }, + { "nonnull", SANITIZE_NONNULL, sizeof "nonnull" - 1 }, + { "returns-nonnull", SANITIZE_RETURNS_NONNULL, + sizeof "returns-nonnull" - 1 }, { NULL, 0, 0 } }; const char *comma; @@ -1511,7 +1514,8 @@ common_handle_option (struct gcc_options /* When instrumenting the pointers, we don't want to remove the null pointer checks. */ - if (flag_sanitize & SANITIZE_NULL) + if (flag_sanitize & (SANITIZE_NULL | SANITIZE_NONNULL + | SANITIZE_RETURNS_NONNULL)) opts->x_flag_delete_null_pointer_checks = 0; break; } --- gcc/sanitizer.def.jj 2014-06-20 23:26:23.000000000 +0200 +++ gcc/sanitizer.def 2014-06-26 16:52:34.644801331 +0200 @@ -417,3 +417,19 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HAN "__ubsan_handle_out_of_bounds_abort", BT_FN_VOID_PTR_PTR, ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_NONNULL_ARG, + "__ubsan_handle_nonnull_arg", + BT_FN_VOID_PTR_PTRMODE, + ATTR_COLD_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_NONNULL_ARG_ABORT, + "__ubsan_handle_nonnull_arg_abort", + BT_FN_VOID_PTR_PTRMODE, + ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN, + "__ubsan_handle_nonnull_return", + BT_FN_VOID_PTR, + ATTR_COLD_NOTHROW_LEAF_LIST) +DEF_SANITIZER_BUILTIN(BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN_ABORT, + "__ubsan_handle_nonnull_return_abort", + BT_FN_VOID_PTR, + ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST) --- gcc/ubsan.c.jj 2014-06-20 23:26:24.000000000 +0200 +++ gcc/ubsan.c 2014-06-26 18:23:28.190191706 +0200 @@ -997,6 +997,7 @@ instrument_bool_enum_load (gimple_stmt_i } gimple_set_location (g, loc); gsi_insert_before (&gsi2, g, GSI_SAME_STMT); + *gsi = gsi_for_stmt (stmt); } /* Instrument float point-to-integer conversion. TYPE is an integer type of @@ -1121,6 +1122,117 @@ ubsan_instrument_float_cast (location_t fn, integer_zero_node); } +/* Instrument values passed to function arguments with nonnull attribute. */ + +static void +instrument_nonnull_arg (gimple_stmt_iterator *gsi) +{ + gimple stmt = gsi_stmt (*gsi); + location_t loc = gimple_location (stmt); + /* infer_nonnull_range needs flag_delete_null_pointer_checks set, + while for nonnull sanitization it is clear. */ + int save_flag_delete_null_pointer_checks = flag_delete_null_pointer_checks; + flag_delete_null_pointer_checks = 1; + for (unsigned int i = 0; i < gimple_call_num_args (stmt); i++) + { + tree arg = gimple_call_arg (stmt, i); + if (POINTER_TYPE_P (TREE_TYPE (arg)) + && infer_nonnull_range (stmt, arg, false, true)) + { + gimple g; + if (!is_gimple_val (arg)) + { + g = gimple_build_assign (make_ssa_name (TREE_TYPE (arg), NULL), + arg); + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + arg = gimple_assign_lhs (g); + } + + basic_block then_bb, fallthru_bb; + *gsi = create_cond_insert_point (gsi, true, false, true, + &then_bb, &fallthru_bb); + g = gimple_build_cond (EQ_EXPR, arg, + build_zero_cst (TREE_TYPE (arg)), + NULL_TREE, NULL_TREE); + gimple_set_location (g, loc); + gsi_insert_after (gsi, g, GSI_NEW_STMT); + + *gsi = gsi_after_labels (then_bb); + if (flag_sanitize_undefined_trap_on_error) + g = gimple_build_call (builtin_decl_explicit (BUILT_IN_TRAP), 0); + else + { + tree data = ubsan_create_data ("__ubsan_nonnull_arg_data", + &loc, NULL, NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + enum built_in_function bcode + = flag_sanitize_recover + ? BUILT_IN_UBSAN_HANDLE_NONNULL_ARG + : BUILT_IN_UBSAN_HANDLE_NONNULL_ARG_ABORT; + tree fn = builtin_decl_explicit (bcode); + + g = gimple_build_call (fn, 2, data, + build_int_cst (pointer_sized_int_node, + i + 1)); + } + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + } + *gsi = gsi_for_stmt (stmt); + } + flag_delete_null_pointer_checks = save_flag_delete_null_pointer_checks; +} + +/* Instrument returns in functions with returns_nonnull attribute. */ + +static void +instrument_nonnull_return (gimple_stmt_iterator *gsi) +{ + gimple stmt = gsi_stmt (*gsi); + location_t loc = gimple_location (stmt); + tree arg = gimple_return_retval (stmt); + /* infer_nonnull_range needs flag_delete_null_pointer_checks set, + while for nonnull return sanitization it is clear. */ + int save_flag_delete_null_pointer_checks = flag_delete_null_pointer_checks; + flag_delete_null_pointer_checks = 1; + if (arg + && POINTER_TYPE_P (TREE_TYPE (arg)) + && is_gimple_val (arg) + && infer_nonnull_range (stmt, arg, false, true)) + { + basic_block then_bb, fallthru_bb; + *gsi = create_cond_insert_point (gsi, true, false, true, + &then_bb, &fallthru_bb); + gimple g = gimple_build_cond (EQ_EXPR, arg, + build_zero_cst (TREE_TYPE (arg)), + NULL_TREE, NULL_TREE); + gimple_set_location (g, loc); + gsi_insert_after (gsi, g, GSI_NEW_STMT); + + *gsi = gsi_after_labels (then_bb); + if (flag_sanitize_undefined_trap_on_error) + g = gimple_build_call (builtin_decl_explicit (BUILT_IN_TRAP), 0); + else + { + tree data = ubsan_create_data ("__ubsan_nonnull_return_data", + &loc, NULL, NULL_TREE); + data = build_fold_addr_expr_loc (loc, data); + enum built_in_function bcode + = flag_sanitize_recover + ? BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN + : BUILT_IN_UBSAN_HANDLE_NONNULL_RETURN_ABORT; + tree fn = builtin_decl_explicit (bcode); + + g = gimple_build_call (fn, 1, data); + } + gimple_set_location (g, loc); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + *gsi = gsi_for_stmt (stmt); + } + flag_delete_null_pointer_checks = save_flag_delete_null_pointer_checks; +} + namespace { const pass_data pass_data_ubsan = @@ -1148,7 +1260,8 @@ public: virtual bool gate (function *) { return flag_sanitize & (SANITIZE_NULL | SANITIZE_SI_OVERFLOW - | SANITIZE_BOOL | SANITIZE_ENUM); + | SANITIZE_BOOL | SANITIZE_ENUM + | SANITIZE_NONNULL | SANITIZE_RETURNS_NONNULL); } virtual unsigned int execute (function *); @@ -1188,7 +1301,25 @@ pass_ubsan::execute (function *fun) if (flag_sanitize & (SANITIZE_BOOL | SANITIZE_ENUM) && gimple_assign_load_p (stmt)) - instrument_bool_enum_load (&gsi); + { + instrument_bool_enum_load (&gsi); + bb = gimple_bb (stmt); + } + + if ((flag_sanitize & SANITIZE_NONNULL) + && is_gimple_call (stmt) + && !gimple_call_internal_p (stmt)) + { + instrument_nonnull_arg (&gsi); + bb = gimple_bb (stmt); + } + + if ((flag_sanitize & SANITIZE_RETURNS_NONNULL) + && gimple_code (stmt) == GIMPLE_RETURN) + { + instrument_nonnull_return (&gsi); + bb = gimple_bb (stmt); + } gsi_next (&gsi); } --- gcc/testsuite/c-c++-common/ubsan/nonnull-1.c.jj 2014-06-26 18:19:27.655457620 +0200 +++ gcc/testsuite/c-c++-common/ubsan/nonnull-1.c 2014-06-26 18:27:22.038955861 +0200 @@ -0,0 +1,38 @@ +/* { dg-do run } */ +/* { dg-options "-fsanitize=nonnull,returns-nonnull" } */ + +int q, r; +void *a, *b, *c = (void *) &q, *d, *e, *f = (void *) &q, *g, *h; + +__attribute__((returns_nonnull, nonnull (1, 3))) +void * +foo (void *p, void *q, void *r) +{ + a = p; + b = r; + return q; +} + +int +bar (const void *a, const void *b) +{ + int c = *(const int *) a; + int d = *(const int *) b; + return c - d; +} + +int +main () +{ + asm volatile ("" : : : "memory"); + d = foo (c, b, c); + e = foo (e, c, f); + g = foo (c, f, g); + __builtin_memset (d, '\0', q); + return 0; +} + +/* { dg-output "\.c:13:\[0-9]*:\[^\n\r]*null return value where non-null required\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*\.c:29:\[0-9]*:\[^\n\r]*null argument where non-null required .argument 1.\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*\.c:30:\[0-9]*:\[^\n\r]*null argument where non-null required .argument 3.\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*\.c:31:\[0-9]*:\[^\n\r]*null argument where non-null required .argument 1." } */ --- gcc/testsuite/c-c++-common/ubsan/nonnull-2.c.jj 2014-06-26 18:28:36.151565589 +0200 +++ gcc/testsuite/c-c++-common/ubsan/nonnull-2.c 2014-06-26 18:29:36.392248392 +0200 @@ -0,0 +1,36 @@ +/* { dg-do run } */ +/* { dg-shouldfail "ubsan" } */ +/* { dg-options "-fsanitize=undefined -fno-sanitize-recover" } */ + +int q, r; +void *a, *b, *c = (void *) &q, *d, *e, *f = (void *) &q, *g, *h; + +__attribute__((returns_nonnull, nonnull (1, 3))) +void * +foo (void *p, void *q, void *r) +{ + a = p; + b = r; + return q; +} + +int +bar (const void *a, const void *b) +{ + int c = *(const int *) a; + int d = *(const int *) b; + return c - d; +} + +int +main () +{ + asm volatile ("" : : : "memory"); + d = foo (c, b, c); + e = foo (e, c, f); + g = foo (c, f, g); + __builtin_memset (d, '\0', q); + return 0; +} + +/* { dg-output "\.c:14:\[0-9]*:\[^\n\r]*null return value where non-null required" } */ --- gcc/testsuite/c-c++-common/ubsan/nonnull-3.c.jj 2014-06-26 18:29:50.883172086 +0200 +++ gcc/testsuite/c-c++-common/ubsan/nonnull-3.c 2014-06-26 18:31:39.152601753 +0200 @@ -0,0 +1,36 @@ +/* { dg-do run } */ +/* { dg-shouldfail "ubsan" } */ +/* { dg-options "-fsanitize=undefined -fno-sanitize-recover" } */ + +int q, r; +void *a, *b, *c = (void *) &q, *d, *e, *f = (void *) &q, *g, *h; + +__attribute__((returns_nonnull, nonnull (1, 3))) +void * +foo (void *p, void *q, void *r) +{ + a = p; + b = r; + return q; +} + +int +bar (const void *a, const void *b) +{ + int c = *(const int *) a; + int d = *(const int *) b; + return c - d; +} + +int +main () +{ + asm volatile ("" : : : "memory"); + d = foo (c, (void *) &r, c); + e = foo (e, c, f); + g = foo (c, f, g); + __builtin_memset (d, '\0', q); + return 0; +} + +/* { dg-output "\.c:30:\[0-9]*:\[^\n\r]*null argument where non-null required .argument 1." } */ --- gcc/testsuite/c-c++-common/ubsan/nonnull-4.c.jj 2014-06-26 18:31:49.118552359 +0200 +++ gcc/testsuite/c-c++-common/ubsan/nonnull-4.c 2014-06-26 18:32:13.526423944 +0200 @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-shouldfail "ubsan" } */ +/* { dg-options "-fsanitize=undefined -fsanitize-undefined-trap-on-error" } */ + +int q, r; +void *a, *b, *c = (void *) &q, *d, *e, *f = (void *) &q, *g, *h; + +__attribute__((returns_nonnull, nonnull (1, 3))) +void * +foo (void *p, void *q, void *r) +{ + a = p; + b = r; + return q; +} + +int +bar (const void *a, const void *b) +{ + int c = *(const int *) a; + int d = *(const int *) b; + return c - d; +} + +int +main () +{ + asm volatile ("" : : : "memory"); + d = foo (c, b, c); + e = foo (e, c, f); + g = foo (c, f, g); + __builtin_memset (d, '\0', q); + return 0; +} --- gcc/testsuite/c-c++-common/ubsan/nonnull-5.c.jj 2014-06-26 18:32:23.284367076 +0200 +++ gcc/testsuite/c-c++-common/ubsan/nonnull-5.c 2014-06-26 18:33:42.154968619 +0200 @@ -0,0 +1,34 @@ +/* { dg-do run } */ +/* { dg-shouldfail "ubsan" } */ +/* { dg-options "-fsanitize=undefined -fsanitize-undefined-trap-on-error" } */ + +int q, r; +void *a, *b, *c = (void *) &q, *d, *e, *f = (void *) &q, *g, *h; + +__attribute__((returns_nonnull, nonnull (1, 3))) +void * +foo (void *p, void *q, void *r) +{ + a = p; + b = r; + return q; +} + +int +bar (const void *a, const void *b) +{ + int c = *(const int *) a; + int d = *(const int *) b; + return c - d; +} + +int +main () +{ + asm volatile ("" : : : "memory"); + d = foo (c, (void *) &r, c); + e = foo (e, c, f); + g = foo (c, f, g); + __builtin_memset (d, '\0', q); + return 0; +} --- libsanitizer/ubsan/ubsan_handlers.cc.jj 2013-12-05 12:04:32.000000000 +0100 +++ libsanitizer/ubsan/ubsan_handlers.cc 2014-06-26 17:03:48.018251367 +0200 @@ -277,3 +277,31 @@ void __ubsan::__ubsan_handle_function_ty __ubsan_handle_function_type_mismatch(Data, Function); Die(); } + +void __ubsan::__ubsan_handle_nonnull_arg(NonNullArgData *Data, uptr ArgNo) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Diag(Loc, DL_Error, "null argument where non-null required " + "(argument %0)") << ArgNo; +} + +void __ubsan::__ubsan_handle_nonnull_arg_abort(NonNullArgData *Data, + uptr ArgNo) { + __ubsan::__ubsan_handle_nonnull_arg(Data, ArgNo); + Die(); +} + +void __ubsan::__ubsan_handle_nonnull_return(NonNullRetData *Data) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Diag(Loc, DL_Error, "null return value where non-null required"); +} + +void __ubsan::__ubsan_handle_nonnull_return_abort(NonNullRetData *Data) { + __ubsan::__ubsan_handle_nonnull_return(Data); + Die(); +} --- libsanitizer/ubsan/ubsan_handlers.h.jj 2013-12-05 12:04:32.000000000 +0100 +++ libsanitizer/ubsan/ubsan_handlers.h 2014-06-26 17:02:03.330803480 +0200 @@ -119,6 +119,20 @@ RECOVERABLE(function_type_mismatch, FunctionTypeMismatchData *Data, ValueHandle Val) +struct NonNullArgData { + SourceLocation Loc; +}; + +/// \brief Handle passing null to function argument with nonnull attribute. +RECOVERABLE(nonnull_arg, NonNullArgData *Data, uptr ArgNo) + +struct NonNullRetData { + SourceLocation Loc; +}; + +/// \brief Handle returning null from function with returns_nonnull attribute. +RECOVERABLE(nonnull_return, NonNullRetData *Data) + } #endif // UBSAN_HANDLERS_H Jakub