1 /* 2 * Strictly speaking, this is not a test. But it can report during test 3 * runs so relative performace can be measured. 4 */ 5 #define _GNU_SOURCE 6 #include <assert.h> 7 #include <limits.h> 8 #include <stdbool.h> 9 #include <stddef.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <time.h> 13 #include <unistd.h> 14 #include <linux/filter.h> 15 #include <linux/seccomp.h> 16 #include <sys/param.h> 17 #include <sys/prctl.h> 18 #include <sys/syscall.h> 19 #include <sys/types.h> 20 21 #include "../kselftest.h" 22 23 unsigned long long timing(clockid_t clk_id, unsigned long long samples) 24 { 25 struct timespec start, finish; 26 unsigned long long i; 27 pid_t pid, ret; 28 29 pid = getpid(); 30 assert(clock_gettime(clk_id, &start) == 0); 31 for (i = 0; i < samples; i++) { 32 ret = syscall(__NR_getpid); 33 assert(pid == ret); 34 } 35 assert(clock_gettime(clk_id, &finish) == 0); 36 37 i = finish.tv_sec - start.tv_sec; 38 i *= 1000000000ULL; 39 i += finish.tv_nsec - start.tv_nsec; 40 41 ksft_print_msg("%lu.%09lu - %lu.%09lu = %llu (%.1fs)\n", 42 finish.tv_sec, finish.tv_nsec, 43 start.tv_sec, start.tv_nsec, 44 i, (double)i / 1000000000.0); 45 46 return i; 47 } 48 49 unsigned long long calibrate(void) 50 { 51 struct timespec start, finish; 52 unsigned long long i, samples, step = 9973; 53 pid_t pid, ret; 54 int seconds = 15; 55 56 ksft_print_msg("Calibrating sample size for %d seconds worth of syscalls ...\n", seconds); 57 58 samples = 0; 59 pid = getpid(); 60 assert(clock_gettime(CLOCK_MONOTONIC, &start) == 0); 61 do { 62 for (i = 0; i < step; i++) { 63 ret = syscall(__NR_getpid); 64 assert(pid == ret); 65 } 66 assert(clock_gettime(CLOCK_MONOTONIC, &finish) == 0); 67 68 samples += step; 69 i = finish.tv_sec - start.tv_sec; 70 i *= 1000000000ULL; 71 i += finish.tv_nsec - start.tv_nsec; 72 } while (i < 1000000000ULL); 73 74 return samples * seconds; 75 } 76 77 bool approx(int i_one, int i_two) 78 { 79 double one = i_one, one_bump = one * 0.01; 80 double two = i_two, two_bump = two * 0.01; 81 82 one_bump = one + MAX(one_bump, 2.0); 83 two_bump = two + MAX(two_bump, 2.0); 84 85 /* Equal to, or within 1% or 2 digits */ 86 if (one == two || 87 (one > two && one <= two_bump) || 88 (two > one && two <= one_bump)) 89 return true; 90 return false; 91 } 92 93 bool le(int i_one, int i_two) 94 { 95 if (i_one <= i_two) 96 return true; 97 return false; 98 } 99 100 long compare(const char *name_one, const char *name_eval, const char *name_two, 101 unsigned long long one, bool (*eval)(int, int), unsigned long long two, 102 bool skip) 103 { 104 bool good; 105 106 if (skip) { 107 ksft_test_result_skip("%s %s %s\n", name_one, name_eval, 108 name_two); 109 return 0; 110 } 111 112 ksft_print_msg("\t%s %s %s (%lld %s %lld): ", name_one, name_eval, name_two, 113 (long long)one, name_eval, (long long)two); 114 if (one > INT_MAX) { 115 ksft_print_msg("Miscalculation! Measurement went negative: %lld\n", (long long)one); 116 good = false; 117 goto out; 118 } 119 if (two > INT_MAX) { 120 ksft_print_msg("Miscalculation! Measurement went negative: %lld\n", (long long)two); 121 good = false; 122 goto out; 123 } 124 125 good = eval(one, two); 126 printf("%s\n", good ? "✔️" : "❌"); 127 128 out: 129 ksft_test_result(good, "%s %s %s\n", name_one, name_eval, name_two); 130 131 return good ? 0 : 1; 132 } 133 134 int main(int argc, char *argv[]) 135 { 136 struct sock_filter bitmap_filter[] = { 137 BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, nr)), 138 BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), 139 }; 140 struct sock_fprog bitmap_prog = { 141 .len = (unsigned short)ARRAY_SIZE(bitmap_filter), 142 .filter = bitmap_filter, 143 }; 144 struct sock_filter filter[] = { 145 BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, args[0])), 146 BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), 147 }; 148 struct sock_fprog prog = { 149 .len = (unsigned short)ARRAY_SIZE(filter), 150 .filter = filter, 151 }; 152 153 long ret, bits; 154 unsigned long long samples, calc; 155 unsigned long long native, filter1, filter2, bitmap1, bitmap2; 156 unsigned long long entry, per_filter1, per_filter2; 157 bool skip = false; 158 159 setbuf(stdout, NULL); 160 161 ksft_print_header(); 162 ksft_set_plan(7); 163 164 ksft_print_msg("Running on:\n"); 165 ksft_print_msg(""); 166 system("uname -a"); 167 168 ksft_print_msg("Current BPF sysctl settings:\n"); 169 /* Avoid using "sysctl" which may not be installed. */ 170 ksft_print_msg(""); 171 system("grep -H . /proc/sys/net/core/bpf_jit_enable"); 172 ksft_print_msg(""); 173 system("grep -H . /proc/sys/net/core/bpf_jit_harden"); 174 175 if (argc > 1) 176 samples = strtoull(argv[1], NULL, 0); 177 else 178 samples = calibrate(); 179 180 ksft_print_msg("Benchmarking %llu syscalls...\n", samples); 181 182 /* Native call */ 183 native = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples; 184 ksft_print_msg("getpid native: %llu ns\n", native); 185 186 ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 187 assert(ret == 0); 188 189 /* One filter resulting in a bitmap */ 190 ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog); 191 assert(ret == 0); 192 193 bitmap1 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples; 194 ksft_print_msg("getpid RET_ALLOW 1 filter (bitmap): %llu ns\n", bitmap1); 195 196 /* Second filter resulting in a bitmap */ 197 ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog); 198 assert(ret == 0); 199 200 bitmap2 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples; 201 ksft_print_msg("getpid RET_ALLOW 2 filters (bitmap): %llu ns\n", bitmap2); 202 203 /* Third filter, can no longer be converted to bitmap */ 204 ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); 205 assert(ret == 0); 206 207 filter1 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples; 208 ksft_print_msg("getpid RET_ALLOW 3 filters (full): %llu ns\n", filter1); 209 210 /* Fourth filter, can not be converted to bitmap because of filter 3 */ 211 ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &bitmap_prog); 212 assert(ret == 0); 213 214 filter2 = timing(CLOCK_PROCESS_CPUTIME_ID, samples) / samples; 215 ksft_print_msg("getpid RET_ALLOW 4 filters (full): %llu ns\n", filter2); 216 217 /* Estimations */ 218 #define ESTIMATE(fmt, var, what) do { \ 219 var = (what); \ 220 ksft_print_msg("Estimated " fmt ": %llu ns\n", var); \ 221 if (var > INT_MAX) { \ 222 skip = true; \ 223 ret |= 1; \ 224 } \ 225 } while (0) 226 227 ESTIMATE("total seccomp overhead for 1 bitmapped filter", calc, 228 bitmap1 - native); 229 ESTIMATE("total seccomp overhead for 2 bitmapped filters", calc, 230 bitmap2 - native); 231 ESTIMATE("total seccomp overhead for 3 full filters", calc, 232 filter1 - native); 233 ESTIMATE("total seccomp overhead for 4 full filters", calc, 234 filter2 - native); 235 ESTIMATE("seccomp entry overhead", entry, 236 bitmap1 - native - (bitmap2 - bitmap1)); 237 ESTIMATE("seccomp per-filter overhead (last 2 diff)", per_filter1, 238 filter2 - filter1); 239 ESTIMATE("seccomp per-filter overhead (filters / 4)", per_filter2, 240 (filter2 - native - entry) / 4); 241 242 ksft_print_msg("Expectations:\n"); 243 ret |= compare("native", "≤", "1 bitmap", native, le, bitmap1, 244 skip); 245 bits = compare("native", "≤", "1 filter", native, le, filter1, 246 skip); 247 if (bits) 248 skip = true; 249 250 ret |= compare("per-filter (last 2 diff)", "≈", "per-filter (filters / 4)", 251 per_filter1, approx, per_filter2, skip); 252 253 bits = compare("1 bitmapped", "≈", "2 bitmapped", 254 bitmap1 - native, approx, bitmap2 - native, skip); 255 if (bits) { 256 ksft_print_msg("Skipping constant action bitmap expectations: they appear unsupported.\n"); 257 skip = true; 258 } 259 260 ret |= compare("entry", "≈", "1 bitmapped", entry, approx, 261 bitmap1 - native, skip); 262 ret |= compare("entry", "≈", "2 bitmapped", entry, approx, 263 bitmap2 - native, skip); 264 ret |= compare("native + entry + (per filter * 4)", "≈", "4 filters total", 265 entry + (per_filter1 * 4) + native, approx, filter2, 266 skip); 267 268 if (ret) 269 ksft_print_msg("Saw unexpected benchmark result. Try running again with more samples?\n"); 270 271 ksft_finished(); 272 } 273