1 /*
2 * Copyright (C) 2013-2021 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * This code is a complete clean re-write of the stress tool by
19 * Colin Ian King <colin.king@canonical.com> and attempts to be
20 * backwardly compatible with the stress tool by Amos Waterland
21 * <apw@rossby.metr.ou.edu> but has more stress tests and more
22 * functionality.
23 *
24 */
25 #include "stress-ng.h"
26
27 static const stress_help_t help[] = {
28 { NULL, "efivar N", "start N workers that read EFI variables" },
29 { NULL, "efivar-ops N", "stop after N EFI variable bogo read operations" },
30 { NULL, NULL, NULL }
31 };
32
33 #if defined(__linux__)
34
35 typedef struct {
36 uint16_t varname[512];
37 uint8_t guid[16];
38 uint64_t datalen;
39 uint8_t data[1024];
40 uint64_t status;
41 uint32_t attributes;
42 } __attribute__((packed)) stress_efi_var_t;
43
44 static const char vars[] = "/sys/firmware/efi/vars";
45 static const char efi_vars[] = "/sys/firmware/efi/efivars";
46 static struct dirent **efi_dentries;
47 static bool *efi_ignore;
48 static int dir_count;
49
50 static const char * const efi_sysfs_names[] = {
51 "attributes",
52 "data",
53 "guid",
54 "size"
55 };
56
57 /*
58 * efi_var_ignore()
59 * check for filenames that are not efi vars
60 */
efi_var_ignore(char * d_name)61 static inline bool efi_var_ignore(char *d_name)
62 {
63 if (*d_name == '.')
64 return true;
65 if (strcmp(d_name, "del_var") == 0)
66 return true;
67 if (strcmp(d_name, "new_var") == 0)
68 return true;
69 if (strstr(d_name, "MokListRT"))
70 return true;
71 return false;
72 }
73
74 /*
75 * guid_to_str()
76 * turn efi GUID to a string
77 */
guid_to_str(const uint8_t * guid,char * guid_str,size_t guid_len)78 static inline void guid_to_str(const uint8_t *guid, char *guid_str, size_t guid_len)
79 {
80 if (!guid_str)
81 return;
82
83 if (guid_len > 36)
84 (void)snprintf(guid_str, guid_len,
85 "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
86 "%02x%02x-%02x%02x%02x%02x%02x%02x",
87 guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6],
88 guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]);
89 else
90 *guid_str = '\0';
91 }
92
93 /*
94 * efi_get_varname()
95 * fetch the UEFI variable name in terms of a 8 bit C string
96 */
efi_get_varname(char * dst,const size_t len,const stress_efi_var_t * var)97 static inline void efi_get_varname(char *dst, const size_t len, const stress_efi_var_t *var)
98 {
99 register size_t i = len;
100
101 /*
102 * gcc-9 -Waddress-of-packed-member workaround, urgh, we know
103 * this is always going to be aligned correctly, but gcc-9 whines
104 * so this hack works around it for now.
105 */
106 const uint8_t *src8 = (const uint8_t *)var->varname;
107 const uint16_t *src = (const uint16_t *)src8;
108
109 while ((*src) && (i > 1)) {
110 *dst++ = (char)(*(src++) & 0xff);
111 i--;
112 }
113 *dst = '\0';
114 }
115
116 /*
117 * efi_lseek_read()
118 * perform a lseek and a 1 char read on fd, silently ignore errors
119 */
efi_lseek_read(const int fd,const off_t offset,const int whence)120 static void efi_lseek_read(const int fd, const off_t offset, const int whence)
121 {
122 off_t offret;
123
124 offret = lseek(fd, offset, whence);
125 if (offret != (off_t)-1) {
126 char data[1];
127 ssize_t n;
128
129 n = read(fd, data, sizeof(data));
130 (void)n;
131 }
132 }
133
134 /*
135 * efi_get_data()
136 * read data from a raw efi sysfs entry
137 */
efi_get_data(const stress_args_t * args,const char * varname,const char * field,void * buf,size_t buf_len)138 static int efi_get_data(
139 const stress_args_t *args,
140 const char *varname,
141 const char *field,
142 void *buf,
143 size_t buf_len)
144 {
145 int fd, rc = -1;
146 ssize_t n;
147 char filename[PATH_MAX];
148 struct stat statbuf;
149 off_t offset;
150
151 (void)snprintf(filename, sizeof(filename),
152 "%s/%s/%s", vars, varname, field);
153 if ((fd = open(filename, O_RDONLY)) < 0)
154 return rc;
155
156 if (fstat(fd, &statbuf) < 0) {
157 pr_err("%s: failed to stat %s, errno=%d (%s)\n",
158 args->name, filename, errno, strerror(errno));
159 goto err_vars;
160 }
161
162 (void)memset(buf, 0, buf_len);
163
164 n = read(fd, buf, buf_len);
165 if ((n < 0) && (errno != EIO) && (errno != EAGAIN) && (errno != EINTR)) {
166 pr_err("%s: failed to read %s, errno=%d (%s)\n",
167 args->name, filename, errno, strerror(errno));
168 goto err_vars;
169 }
170
171 rc = 0;
172
173 /*
174 * And exercise the interface for some extra kernel
175 * test coverage
176 */
177 offset = (n > 0) ? stress_mwc32() % n : 0;
178 efi_lseek_read(fd, offset, SEEK_SET);
179
180 offset = (n > 0) ? stress_mwc32() % n : 0;
181 efi_lseek_read(fd, offset, SEEK_END);
182
183 efi_lseek_read(fd, 0, SEEK_SET);
184 efi_lseek_read(fd, offset, SEEK_CUR);
185
186 /*
187 * exercise mmap
188 */
189 {
190 const size_t len = (n > 0) ? (size_t)n : args->page_size;
191 void *ptr;
192
193 ptr = mmap(NULL, len, PROT_READ | PROT_WRITE,
194 MAP_PRIVATE | MAP_ANONYMOUS, fd, 0);
195 if (ptr != MAP_FAILED) {
196 stress_madvise_random(ptr, len);
197 (void)munmap(ptr, len);
198 }
199 }
200
201 #if defined(FIGETBSZ)
202 {
203 int ret, isz;
204
205 ret = ioctl(fd, FIGETBSZ, &isz);
206 (void)ret;
207 }
208 #endif
209 #if defined(FIONREAD)
210 {
211 int ret, isz;
212
213 ret = ioctl(fd, FIONREAD, &isz);
214 (void)ret;
215 }
216 #endif
217
218 err_vars:
219 (void)close(fd);
220
221 return rc;
222 }
223
224 /*
225 * efi_get_variable()
226 * fetch a UEFI variable given its name.
227 */
efi_get_variable(const stress_args_t * args,const char * varname,stress_efi_var_t * var)228 static int efi_get_variable(const stress_args_t *args, const char *varname, stress_efi_var_t *var)
229 {
230 #if defined(FS_IOC_GETFLAGS) && \
231 defined(FS_IOC_SETFLAGS)
232 int flags;
233 #endif
234 int fd, ret, rc = 0;
235 size_t i;
236 ssize_t n;
237 char filename[PATH_MAX];
238 static char data[4096];
239 struct stat statbuf;
240
241 if ((!varname) || (!var))
242 return -1;
243
244 if (efi_get_data(args, varname, "raw_var", var, sizeof(*var)) < 0)
245 rc = -1;
246
247 /* Exercise reading the efi sysfs files */
248 for (i = 0; i < SIZEOF_ARRAY(efi_sysfs_names); i++) {
249 (void)efi_get_data(args, varname, efi_sysfs_names[i], data, sizeof(data));
250 }
251
252 (void)stress_mk_filename(filename, sizeof(filename), efi_vars, varname);
253 if ((fd = open(filename, O_RDONLY)) < 0)
254 return -1;
255
256 ret = fstat(fd, &statbuf);
257 if (ret < 0) {
258 pr_err("%s: failed to stat %s, errno=%d (%s)\n",
259 args->name, filename, errno, strerror(errno));
260 rc = -1;
261 goto err_efi_vars;
262 }
263
264 n = read(fd, data, sizeof(data));
265 if ((n < 0) && (errno != EIO) && (errno != EAGAIN) && (errno != EINTR)) {
266 pr_err("%s: failed to read %s, errno=%d (%s)\n",
267 args->name, filename, errno, strerror(errno));
268 rc = -1;
269 goto err_efi_vars;
270 }
271
272 #if defined(FS_IOC_GETFLAGS) && \
273 defined(FS_IOC_SETFLAGS)
274 ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
275 if (ret < 0) {
276 pr_err("%s: ioctl FS_IOC_GETFLAGS on %s failed, errno=%d (%s)\n",
277 args->name, filename, errno, strerror(errno));
278 rc = -1;
279 goto err_efi_vars;
280 }
281
282 ret = ioctl(fd, FS_IOC_SETFLAGS, &flags);
283 if (ret < 0) {
284 pr_err("%s: ioctl FS_IOC_SETFLAGS on %s failed, errno=%d (%s)\n",
285 args->name, filename, errno, strerror(errno));
286 rc = -1;
287 }
288 #endif
289
290 err_efi_vars:
291 (void)close(fd);
292
293 return rc;
294 }
295
296 /*
297 * efi_vars_get()
298 * read EFI variables
299 */
efi_vars_get(const stress_args_t * args)300 static int efi_vars_get(const stress_args_t *args)
301 {
302 int i;
303
304 for (i = 0; i < dir_count; i++) {
305 stress_efi_var_t var;
306 char *d_name = efi_dentries[i]->d_name;
307 int ret;
308
309 if (efi_ignore[i])
310 continue;
311
312 if (efi_var_ignore(d_name)) {
313 efi_ignore[i] = true;
314 continue;
315 }
316
317 ret = efi_get_variable(args, d_name, &var);
318 if (ret < 0) {
319 efi_ignore[i] = true;
320 continue;
321 }
322
323 if (var.attributes) {
324 char varname[513];
325 char guid_str[37];
326
327 efi_get_varname(varname, sizeof(varname), &var);
328 guid_to_str(var.guid, guid_str, sizeof(guid_str));
329
330 (void)guid_str;
331 } else {
332 efi_ignore[i] = true;
333 }
334 inc_counter(args);
335 }
336
337 return 0;
338 }
339
340 /*
341 * stress_efivar_supported()
342 * check if we can run this as root
343 */
stress_efivar_supported(const char * name)344 static int stress_efivar_supported(const char *name)
345 {
346 DIR *dir;
347
348 if (!stress_check_capability(SHIM_CAP_SYS_ADMIN)) {
349 pr_inf_skip("%s stressor will be skipped, "
350 "need to be running with CAP_SYS_ADMIN "
351 "rights for this stressor\n", name);
352 return -1;
353 }
354
355 dir = opendir(efi_vars);
356 if (!dir) {
357 pr_inf_skip("%s stressor will be skipped, "
358 "need to have access to EFI vars in %s\n",
359 name, vars);
360 return -1;
361 }
362 (void)closedir(dir);
363
364 return 0;
365 }
366
367 /*
368 * stress_efivar()
369 * stress that exercises the efi variables
370 */
stress_efivar(const stress_args_t * args)371 static int stress_efivar(const stress_args_t *args)
372 {
373 pid_t pid;
374 size_t sz;
375
376 efi_dentries = NULL;
377 dir_count = scandir(vars, &efi_dentries, NULL, alphasort);
378 if (!efi_dentries || (dir_count <= 0)) {
379 pr_inf("%s: cannot read EFI vars in %s\n", args->name, vars);
380 return EXIT_SUCCESS;
381 }
382
383 sz = (((size_t)dir_count * sizeof(*efi_ignore)) + args->page_size) & (args->page_size - 1);
384 efi_ignore = mmap(NULL, sz, PROT_READ | PROT_WRITE,
385 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
386 if (efi_ignore == MAP_FAILED) {
387 pr_err("%s: cannot mmap shared memory: %d (%s))\n",
388 args->name, errno, strerror(errno));
389 return EXIT_NO_RESOURCE;
390 }
391
392 stress_set_proc_state(args->name, STRESS_STATE_RUN);
393 again:
394 pid = fork();
395 if (pid < 0) {
396 if (stress_redo_fork(errno))
397 goto again;
398 if (!keep_stressing(args))
399 goto finish;
400 pr_err("%s: fork failed: errno=%d (%s)\n",
401 args->name, errno, strerror(errno));
402 } else if (pid > 0) {
403 int status, ret;
404
405 (void)setpgid(pid, g_pgrp);
406 /* Parent, wait for child */
407 ret = shim_waitpid(pid, &status, 0);
408 if (ret < 0) {
409 if (errno != EINTR)
410 pr_dbg("%s: waitpid(): errno=%d (%s)\n",
411 args->name, errno, strerror(errno));
412 (void)kill(pid, SIGTERM);
413 (void)kill(pid, SIGKILL);
414 (void)shim_waitpid(pid, &status, 0);
415 } else if (WIFSIGNALED(status)) {
416 pr_dbg("%s: child died: %s (instance %d)\n",
417 args->name, stress_strsignal(WTERMSIG(status)),
418 args->instance);
419 return EXIT_FAILURE;
420 }
421 } else if (pid == 0) {
422 (void)setpgid(0, g_pgrp);
423 stress_parent_died_alarm();
424 stress_set_oom_adjustment(args->name, true);
425 (void)sched_settings_apply(true);
426
427 do {
428 efi_vars_get(args);
429 } while (keep_stressing(args));
430 _exit(0);
431 }
432
433 finish:
434 stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
435
436 (void)munmap(efi_ignore, sz);
437 stress_dirent_list_free(efi_dentries, dir_count);
438
439 return EXIT_SUCCESS;
440 }
441
442 stressor_info_t stress_efivar_info = {
443 .stressor = stress_efivar,
444 .supported = stress_efivar_supported,
445 .class = CLASS_OS,
446 .help = help
447 };
448 #else
stress_efivar_supported(const char * name)449 static int stress_efivar_supported(const char *name)
450 {
451 pr_inf_skip("%s stressor will be skipped, "
452 "it is not implemented on this platform\n", name);
453
454 return -1;
455 }
456 stressor_info_t stress_efivar_info = {
457 .stressor = stress_not_implemented,
458 .supported = stress_efivar_supported,
459 .class = CLASS_OS,
460 .help = help
461 };
462 #endif
463