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