diff --git a/Makefile b/Makefile index 8855c974f..e1a4d52ed 100644 --- a/Makefile +++ b/Makefile @@ -416,6 +416,7 @@ validation/%: $(PROGRAMS) FORCE clean: clean-check @rm -f *.[oa] .*.d $(PROGRAMS) version.h smatch + $(MAKE) clean -C cwchash clean-check: @echo " CLEAN" @find validation/ \( -name "*.c.output.*" \ diff --git a/check_memcpy_overflow.c b/check_memcpy_overflow.c index dddc555a2..bccb28d07 100644 --- a/check_memcpy_overflow.c +++ b/check_memcpy_overflow.c @@ -368,5 +368,11 @@ void check_memcpy_overflow(int id) add_function_hook("copy_from_user", &match_limited, &b0_l2); add_function_hook("_copy_from_user", &match_limited, &b0_l2); add_function_hook("__copy_from_user", &match_limited, &b0_l2); + add_function_hook("copy_from_guest", &match_limited, &b0_l2); + add_function_hook("_copy_from_guest", &match_limited, &b0_l2); + add_function_hook("__copy_from_guest", &match_limited, &b0_l2); + add_function_hook("copy_to_guest", &match_limited, &b0_l2); + add_function_hook("_copy_to_guest", &match_limited, &b0_l2); + add_function_hook("__copy_to_guest", &match_limited, &b0_l2); } } diff --git a/check_return_efault.c b/check_return_efault.c index b8ba52bc8..4ffb3144e 100644 --- a/check_return_efault.c +++ b/check_return_efault.c @@ -95,6 +95,10 @@ static void match_return_call(struct expression *ret_value) strstr(cur_func, "_from_user")) return; + if (strstr(cur_func, "_to_guest") || + strstr(cur_func, "_from_guest")) + return; + if (strncmp(cur_func, "copy_from_", 10) == 0 || strncmp(cur_func, "copy_to_", 8) == 0) return; @@ -106,7 +110,11 @@ static void match_return_call(struct expression *ret_value) if (strcmp(fn_name, "copy_to_user") != 0 && strcmp(fn_name, "__copy_to_user") != 0 && strcmp(fn_name, "copy_from_user") != 0 && - strcmp(fn_name, "__copy_from_user")) + strcmp(fn_name, "__copy_from_user") != 0 && + strcmp(fn_name, "copy_to_guest") != 0 && + strcmp(fn_name, "__copy_to_guest") != 0 && + strcmp(fn_name, "copy_from_guest") != 0 && + strcmp(fn_name, "__copy_from_guest") != 0) return; rl = db_return_vals_from_str(get_function()); @@ -128,6 +136,10 @@ void check_return_efault(int id) add_function_assign_hook("__copy_to_user", &match_copy, NULL); add_function_assign_hook("copy_from_user", &match_copy, NULL); add_function_assign_hook("__copy_from_user", &match_copy, NULL); + add_function_assign_hook("copy_from_guest", &match_copy, NULL); + add_function_assign_hook("__copy_from_guest", &match_copy, NULL); + add_function_assign_hook("copy_to_guest", &match_copy, NULL); + add_function_assign_hook("__copy_to_guest", &match_copy, NULL); add_function_assign_hook("clear_user", &match_copy, NULL); add_hook(&match_condition, CONDITION_HOOK); add_hook(&match_return_var, RETURN_HOOK); diff --git a/check_spectre.c b/check_spectre.c index 16301a2ef..8bf64131c 100644 --- a/check_spectre.c +++ b/check_spectre.c @@ -154,16 +154,24 @@ static void array_check(struct expression *expr) struct expression_list *conditions; struct expression *array_expr, *offset; unsigned long long mask; + + int impossible = 0, harmless = 0; + int user = 0, array = 0; + int array_size; char *name; expr = strip_expr(expr); - if (!is_array(expr)) + array = is_array(expr); + if (!array) return; - if (is_impossible_path()) + impossible = is_impossible_path(); + if (suppress_multiple && impossible) return; - if (is_harmless(expr)) + + harmless = is_harmless(expr); + if (suppress_multiple && harmless) return; array_expr = get_array_base(expr); @@ -192,10 +200,12 @@ static void array_check(struct expression *expr) conditions = get_conditions(offset); name = expr_to_str(array_expr); - sm_warning("potential spectre issue '%s' [%s]%s", + sm_warning("potential spectre issue '%s' [%s]%s%s%s", name, is_read(expr) ? "r" : "w", - conditions ? " (local cap)" : ""); + conditions ? " (local cap)" : "", + impossible ? " (impossible)" : "", + harmless ? " (harmless)" : ""); set_spectre_first_half(expr); if (suppress_multiple) diff --git a/cwchash/Makefile b/cwchash/Makefile index 5a1f65cd3..1d4df66f6 100644 --- a/cwchash/Makefile +++ b/cwchash/Makefile @@ -20,7 +20,7 @@ hashtable_itr.o: hashtable_itr.c gcc -g -Wall -O -c hashtable_itr.c -o hashtable_itr.o tidy: - rm *.o + rm -f *.o clean: tidy rm -f tester old_tester diff --git a/smatch_flow.c b/smatch_flow.c index 74c97b4fc..26a462f04 100644 --- a/smatch_flow.c +++ b/smatch_flow.c @@ -910,7 +910,7 @@ int time_parsing_function(void) bool taking_too_long(void) { - if ((ms_since(&outer_fn_start_time) / 1000) > 60 * 5) /* five minutes */ + if ((ms_since(&outer_fn_start_time) / 1000) > 60 * 10) /* 10 minutes */ return 1; return 0; } diff --git a/smatch_implied.c b/smatch_implied.c index 9d4a39131..b74768ff7 100644 --- a/smatch_implied.c +++ b/smatch_implied.c @@ -448,13 +448,13 @@ static int going_too_slow(void) return 1; } - if (time_parsing_function() < 60) { + if (time_parsing_function() < 180) { implications_off = false; return 0; } if (!__inline_fn && printed != cur_func_sym) { - sm_perror("turning off implications after 60 seconds"); + sm_perror("turning off implications after 180 seconds"); printed = cur_func_sym; } implications_off = true; diff --git a/smatch_kernel_user_data.c b/smatch_kernel_user_data.c index 627f7f9a8..818f575f2 100644 --- a/smatch_kernel_user_data.c +++ b/smatch_kernel_user_data.c @@ -45,7 +45,30 @@ static const char *kstr_funcs[] = { static const char *returns_user_data[] = { "simple_strtol", "simple_strtoll", "simple_strtoul", "simple_strtoull", - "kvm_register_read", + "kvm_register_read", "nlmsg_data", "nla_data", "memdup_user", + "kmap_atomic", "skb_network_header", + /* Xen */ + "hvmemul_get_seg_reg", "guest_cpu_user_regs", +}; + +/* In Xen, these functions are called "copy_from_guest" */ +static const char * xen_from_guest_funcs[] = { +"copy_from_guest", "__copy_from_guest", "copy_from_guest_offset", +"copy_from_user_hvm", "__raw_copy_from_guest", "__copy_from_user", +"__hvm_copy", "hvm_copy_from_guest_phys", "hvm_copy_from_guest_virt", +"hvm_fetch_from_guest_virt", "hvm_copy_from_guest_virt_nofault", +"hvm_fetch_from_guest_virt_nofault", +}; + +// in Xen, these functions have user data in their first argument +static const char * xen_hypercalls[] = { +"hvm_memory_op_compat32", "hvm_grant_table_op_compat32", "hvm_vcpu_op_compat32", +"hvm_physdev_op_compat32", "do_event_channel_op", "do_xen_version", +"compat_xen_version", "do_sched_op", "compat_sched_op", +"do_set_timer_op", "compat_set_timer_op", "do_hvm_op", "do_sysctl_op", +"do_tmem_op", "hvm_physdev_op", "hvm_vcpu_op", "hvm_grant_table_op", +"hvm_memory_op", "do_domctl", "arch_do_domctl", "do_sysctl", "arch_do_sysctl", +"do_multicall" }; static struct stree *start_states; @@ -1157,12 +1180,34 @@ static void set_called(const char *name, struct symbol *sym, char *key, char *va set_state(my_call_id, "this_function", NULL, &called); } +static int ends_with(const char *symbol, const char *suffix) +{ + if(!symbol || !suffix) + return 0; + + int l = strnlen(symbol, 128); + int e = strnlen(suffix, 16); + + /* Is the string smaller than the suffix? */ + if(l < e) + return 0; + + /* Check from the end whether the strings match */ + for(int i = 0; i <= e; ++ i) { + if( symbol[l-i] != suffix[e-i]) + return 0; + } + + /* The suffix matches */ + return 1; +} + static void match_syscall_definition(struct symbol *sym) { struct symbol *arg; char *macro; char *name; - int is_syscall = 0; + int is_syscall = 0, i; macro = get_macro_name(sym->pos); if (macro && @@ -1171,14 +1216,34 @@ static void match_syscall_definition(struct symbol *sym) is_syscall = 1; name = get_function(); + + /* Currently only works if there is a name */ + if (!name) + return; + if (!option_no_db && get_state(my_call_id, "this_function", NULL) != &called) { - if (name && strncmp(name, "sys_", 4) == 0) + if (strncmp(name, "sys_", 4) == 0) is_syscall = 1; } - if (name && strncmp(name, "compat_sys_", 11) == 0) + if (strncmp(name, "compat_sys_", 11) == 0) + is_syscall = 1; + + /* Check whether function heuristically matchs hypercalls */ + if (ends_with(name, "_op") && ( + strncmp(name, "do_", 3) == 0 || strncmp(name, "arch_", 5) || strncmp(name, "hvm_", 4) + )) + is_syscall = 1; + else if (strncmp(name, "do_", 3) == 0 && ends_with(name, "_op_compat")) + is_syscall = 1; + else if (strncmp(name, "hvm_", 4) == 0 && ends_with(name, "_op_compat32")) is_syscall = 1; + /* Check against today's list of hypervalls */ + for (i = 0; i < ARRAY_SIZE(xen_hypercalls); i++) + if(strncmp(xen_hypercalls[i], "hvm_", sizeof(xen_hypercalls[i]))) + is_syscall = 1; + if (!is_syscall) return; @@ -1347,6 +1412,25 @@ void register_kernel_user_data(int id) add_function_hook("copy_from_user", &match_user_copy, INT_PTR(0)); add_function_hook("__copy_from_user", &match_user_copy, INT_PTR(0)); add_function_hook("memcpy_fromiovec", &match_user_copy, INT_PTR(0)); + + /* In Xen, these functions are called "copy_from_guest" */ + for (i = 0; i < ARRAY_SIZE(xen_from_guest_funcs); i++) + add_function_hook(xen_from_guest_funcs[i], &match_user_copy, INT_PTR(0)); + + /* Xen hvm read functions */ + add_function_hook("hvmemul_do_mmio", &match_user_copy, INT_PTR(5)); + add_function_hook("hvmemul_do_direct_read", &match_user_copy, INT_PTR(4)); + add_function_hook("hvmemul_do_io", &match_user_copy, INT_PTR(8)); + add_function_hook("hvmemul_read_cr", &match_user_copy, INT_PTR(2)); + add_function_hook("hvm_msr_read_intercept", &match_user_copy, INT_PTR(2)); + + /* Extra functions where we know tainted data is passed */ + for (i = 0; i < ARRAY_SIZE(xen_hypercalls); i++) + add_function_hook(xen_hypercalls[i], &match_user_copy, INT_PTR(1)); + + /* Xen equivalent to kvm_register_read */ + add_function_assign_hook("acpi_hw_register_read", &match_user_copy, NULL); + for (i = 0; i < ARRAY_SIZE(kstr_funcs); i++) add_function_hook_late(kstr_funcs[i], &match_user_copy, INT_PTR(2)); add_function_hook("usb_control_msg", &match_user_copy, INT_PTR(6)); diff --git a/smatch_math.c b/smatch_math.c index 55f5fcef0..c8d00f21d 100644 --- a/smatch_math.c +++ b/smatch_math.c @@ -1501,7 +1501,7 @@ static bool get_rl_sval(struct expression *expr, int implied, int *recurse_cnt, if (!expr) return false; - if (++(*recurse_cnt) >= 200) + if (++(*recurse_cnt) >= 500) return false; switch(expr->type) { @@ -1636,7 +1636,7 @@ static bool get_rl_helper(struct expression *expr, int implied, struct range_lis struct { struct expression *expr; sval_t sval; -} cached_results[24]; +} cached_results[72]; static int cache_idx; void clear_math_cache(void) diff --git a/smatch_scripts/build_xen_data.sh b/smatch_scripts/build_xen_data.sh new file mode 100755 index 000000000..f39cdc635 --- /dev/null +++ b/smatch_scripts/build_xen_data.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +PROJECT=kernel + +function usage +{ + echo + echo "Usage: $0" + echo "Updates the smatch_data/ directory and builds the smatch database" + echo + exit 1 +} + +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + usage +fi + +SCRIPT_DIR=$(dirname $0) +if [ -e $SCRIPT_DIR/../smatch -a -d xen ]; then + CMD=$SCRIPT_DIR/../smatch + DATA_DIR=$(readlink -e $SCRIPT_DIR/../smatch_data) +else + echo "This script should be located in the smatch_scripts/ subdirectory of the smatch source." + echo "It should be run from the root of a kernel source tree." + exit 1 +fi + +# If someone is building the database for the first time then make sure all the +# required packages are installed +if [ ! -e smatch_db.sqlite ]; then + [ -e smatch_warns.txt ] || touch smatch_warns.txt + if ! $DATA_DIR/db/create_db.sh -p=kernel smatch_warns.txt; then + echo "Hm... Not working. Make sure you have all the sqlite3 packages" + echo "And the sqlite3 libraries for Perl and Python" + exit 1 + fi +fi + +$SCRIPT_DIR/test_xen.sh --full-path --call-tree --info --spammy --data=$DATA_DIR + +for i in $SCRIPT_DIR/gen_*; do + $i smatch_warns.txt -p=kernel +done + +mv ${PROJECT}.* $DATA_DIR + +$DATA_DIR/db/create_db.sh -p=kernel smatch_warns.txt diff --git a/smatch_scripts/test_kernel.sh b/smatch_scripts/test_kernel.sh index d254c56a6..4480716ab 100755 --- a/smatch_scripts/test_kernel.sh +++ b/smatch_scripts/test_kernel.sh @@ -66,7 +66,7 @@ fi make $KERNEL_ARCH $KERNEL_CROSS_COMPILE clean find -name \*.c.smatch -exec rm \{\} \; make $KERNEL_ARCH $KERNEL_CROSS_COMPILE -j${NR_CPU} $ENDIAN -k CHECK="$CMD -p=kernel --file-output --succeed $*" \ - C=1 $BUILD_PARAM $TARGET 2>&1 | tee $LOG + C=1 $BUILD_PARAM $TARGET $SMATCH_MAKE_PARAMS 2>&1 | tee $LOG BUILD_STATUS=${PIPESTATUS[0]} find -name \*.c.smatch -exec cat \{\} \; -exec rm \{\} \; > $WLOG find -name \*.c.smatch.sql -exec cat \{\} \; -exec rm \{\} \; > $WLOG.sql diff --git a/smatch_scripts/test_xen.sh b/smatch_scripts/test_xen.sh new file mode 100755 index 000000000..9fa11608d --- /dev/null +++ b/smatch_scripts/test_xen.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +NR_CPU=$(cat /proc/cpuinfo | grep ^processor | wc -l) +WLOG="smatch_warns.txt" +LOG="smatch_compile.warns" + +function usage +{ + echo + echo "Usage: $0 [smatch options]" + echo "Compiles the xen with -j${NR_CPU}" + echo " available options:" + echo " --endian : enable endianess check" + echo " --log {FILE} : Output compile log to file, default is: $LOG" + echo " --wlog {FILE} : Output warnigs to file, default is: $WLOG" + echo " --help : Show this usage" + exit 1 +} + +while true; do + if [[ "$1" == "--endian" ]]; then + ENDIAN="CF=-D__CHECK_ENDIAN__" + shift + elif [[ "$1" == "--log" ]]; then + shift + LOG="$1" + shift + elif [[ "$1" == "--wlog" ]]; then + shift + WLOG="$1" + shift + elif [[ "$1" == "--help" ]]; then + usage + else + break + fi +done + +SCRIPT_DIR=$(dirname $0) +if [ -e $SCRIPT_DIR/../smatch ]; then + cp $SCRIPT_DIR/../smatch $SCRIPT_DIR/../bak.smatch + CMD=$SCRIPT_DIR/../bak.smatch +elif which smatch | grep smatch >/dev/null; then + CMD=smatch +else + echo "Smatch binary not found." + exit 1 +fi + +if ! command -v one-line-scan &>/dev/null; then + echo "one-line-scan binary not found." + exit 1 +fi + +# start fresh, remote .smatch files +make clean -C xen -j $NR_CPU +find -name \*.c.smatch -exec rm \{\} \; +rm -rf SMATCH + +# tell where the data base file resides +touch smatch/smatch_db.sqlite +export SMATCH_DB=$(readlink -e smatch/smatch_db.sqlite) + +# set kernel, file-output, and everything else passed to this script to smatch +export SMATCH_EXTRA_ARG="-p=kernel --file-output $*" + +# compile xen with one-line-scan +one-line-scan -o SMATCH --use-existing --smatch --no-gotocc --no-analysis \ + ${SMATCH_ONE_LINE_SCAN_ARSG:-} \ + -- make xen -j $NR_CPU -B | tee $LOG + +find -name \*.c.smatch -exec cat \{\} \; -exec rm \{\} \; >$WLOG +find -name \*.c.smatch.sql -exec cat \{\} \; -exec rm \{\} \; >$WLOG.sql +find -name \*.c.smatch.caller_info -exec cat \{\} \; -exec rm \{\} \; >$WLOG.caller_info + +echo "Done. The warnings are saved to $WLOG" diff --git a/smatch_scripts/xen/detect-xen-spectre-candidates.sh b/smatch_scripts/xen/detect-xen-spectre-candidates.sh new file mode 100755 index 000000000..2286994ce --- /dev/null +++ b/smatch_scripts/xen/detect-xen-spectre-candidates.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Copyright (C) 2018 Amazon.com, Inc. or its affiliates. +# Author: Norbert Manthey +# +# This script show cases how smatch can be run for Xen. +# You want to run this on a big machine, as Xen will be recompiled many times +# This script writes a file last_smatch_spectre_warns.txt, which will contain +# the spectre v1 candidates of smatch. +# +# Keep this script in its directory, but call it from the Xen root directory! + +# Number of smatch iterations (guest taint improves per iteration) +MAX_ITERATIONS=8 + +# In case something breaks, we want to stop +set -e -u + +# Where is this script located +SCRIPT=$(readlink -e "$0") +SCRIPT_DIR=$(dirname "$SCRIPT") + +# Make sure we're in the Xen directory and Xen builds +echo "Check whether Xen builds ..." +make xen -j $(nproc) + +if [ ! -d one-line-scan ]; then + echo "Make one-line-scan tool available ..." + git clone https://github.com/awslabs/one-line-scan.git +fi + +# Tell environment about tools, and check whether they work +export PATH=$PATH:$(pwd)/smatch:$(pwd)/one-line-scan +echo "Test availability of smatch and one-line-scan ..." +for tool in smatch one-line-scan; do + if ! command -v "$tool" &>/dev/null; then + echo "Cannot find tool $tool, abort" + exit 1 + fi +done +echo "Found all required tools, continue." + +# Initialize variables for analysis +START=$SECONDS # start timestampe to print timing +OLD=0 # number of defects found in last iteration +NEW=0 # number of defects found in current iteration +I=0 # current iteration +BUILD_STATUS=0 # status of the analysis job + +# Repeat analysis at most $MAX_ITERATIONS times +echo "Start Xen analysis with smatch, use $MAX_ITERATIONS iterations" +while [ "$I" -lt $MAX_ITERATIONS ]; do + OLD=$NEW + I=$((I + 1)) + # Write a log per iteration + FULL_SPECTRE=1 ./smatch/smatch_scripts/build_xen_data.sh &>smatch-build-$I.log + BUILD_STATUS=$? + echo "build iteration $I with status $BUILD_STATUS" + [ "$BUILD_STATUS" -eq 0 ] || exit $BUILD_STATUS + + # Keep results of last iteration around, in case the script is stopped early + grep spectre smatch_warns.txt | sort -u >last_smatch_spectre_warns.txt + + # We are only interested in spectre issues for now + NEW=$(cat last_smatch_spectre_warns.txt | wc -l) + NOW=$SECONDS + echo "new amount of defects: $NEW (last: $OLD) at iter $I after $((NOW - START))" + + # Check whether we found more defects + [ "$NEW" -ne "$OLD" ] || break + +done |& tee full-smatch.log + +exit $BUILD_STATUS diff --git a/validation/sm_guest_data1.c b/validation/sm_guest_data1.c new file mode 100644 index 000000000..0528c9705 --- /dev/null +++ b/validation/sm_guest_data1.c @@ -0,0 +1,30 @@ +#include "check_debug.h" + +int copy_from_guest(void *dest, void *src, int size); + +struct my_struct { + int x, y; +}; + +void *pointer; + +void copy_stuff(struct my_struct *foo) +{ + copy_from_guest(foo, pointer, sizeof(*foo)); +} + +void test(void) +{ + struct my_struct foo; + + copy_stuff(&foo); + __smatch_user_rl(foo.x); +} +/* + * check-name: smatch user data #1 + * check-command: smatch -p=kernel -I.. sm_user_data1.c + * + * check-output-start +sm_user_data1.c:21 test() user rl: 'foo.x' = 's32min-s32max' + * check-output-end + */ diff --git a/validation/sm_guest_data2.c b/validation/sm_guest_data2.c new file mode 100644 index 000000000..5bf845d12 --- /dev/null +++ b/validation/sm_guest_data2.c @@ -0,0 +1,32 @@ +#include "check_debug.h" + +int copy_from_guest(void *dest, void *src, int size){} + +struct my_struct { + int x, y; +}; + +void *pointer; +struct my_struct *dest; + +struct my_struct *returns_copy(void) +{ + copy_from_guest(dest, pointer, sizeof(*dest)); + return dest; +} + +struct my_struct *a; +void test(void) +{ + a = returns_copy(); + __smatch_user_rl(a->x); +} + +/* + * check-name: smatch user data #2 + * check-command: smatch -p=kernel -I.. sm_user_data2.c + * + * check-output-start +sm_user_data2.c:22 test() user rl: 'a->x' = 's32min-s32max' + * check-output-end + */ diff --git a/validation/sm_guest_data3.c b/validation/sm_guest_data3.c new file mode 100644 index 000000000..f5b80f61a --- /dev/null +++ b/validation/sm_guest_data3.c @@ -0,0 +1,35 @@ +#include "check_debug.h" + +int copy_from_guest(void *dest, void *src, int size){} + +struct my_struct { + int x, y; +}; + +struct my_struct *returns_filter(struct my_struct *p) +{ + return p; +} + +struct my_struct *src, *a, *b; +void test(void) +{ + copy_from_guest(a, src, sizeof(*a)); + b = returns_filter(a); + __smatch_user_rl(b->y); + b = returns_filter(src); + __smatch_user_rl(b->y); + b = returns_filter(a); + __smatch_user_rl(b->y); +} + +/* + * check-name: smatch user data #3 + * check-command: smatch -p=kernel -I.. sm_user_data3.c + * + * check-output-start +sm_user_data3.c:19 test() user rl: 'b->y' = 's32min-s32max' +sm_user_data3.c:21 test() user rl: 'b->y' = '' +sm_user_data3.c:23 test() user rl: 'b->y' = 's32min-s32max' + * check-output-end + */ diff --git a/validation/sm_guest_data4.c b/validation/sm_guest_data4.c new file mode 100644 index 000000000..91ad6d116 --- /dev/null +++ b/validation/sm_guest_data4.c @@ -0,0 +1,45 @@ +#include "check_debug.h" + +int copy_from_guest(void *dest, void *src, int size); + +struct ear { + int x, y; +}; + +void *src; +int returns_user_data(void) +{ + int x; + + copy_from_guest(&x, src, sizeof(int)); + return x; +} + +struct ear *dest; +struct ear *returns_user_member(void) +{ + copy_from_guest(&dest->x, src, sizeof(int)); + return dest; +} +void test(void) +{ + struct ear *p; + int x; + + x = returns_user_data(); + __smatch_user_rl(x); + p = returns_user_member(); + __smatch_user_rl(p); + __smatch_user_rl(p->x); +} + +/* + * check-name: smatch user data #4 + * check-command: smatch -p=kernel -I.. sm_user_data4.c + * + * check-output-start +sm_user_data4.c:30 test() user rl: 'x' = 's32min-s32max' +sm_user_data4.c:32 test() user rl: 'p' = '' +sm_user_data4.c:33 test() user rl: 'p->x' = 's32min-s32max' + * check-output-end + */