1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Test triggering of loading of firmware from different mount |
3 | * namespaces. Expect firmware to be always loaded from the mount |
4 | * namespace of PID 1. */ |
5 | #define _GNU_SOURCE |
6 | #include <errno.h> |
7 | #include <fcntl.h> |
8 | #include <sched.h> |
9 | #include <stdarg.h> |
10 | #include <stdbool.h> |
11 | #include <stdio.h> |
12 | #include <stdlib.h> |
13 | #include <string.h> |
14 | #include <sys/mount.h> |
15 | #include <sys/stat.h> |
16 | #include <sys/types.h> |
17 | #include <sys/wait.h> |
18 | #include <unistd.h> |
19 | |
20 | static char *fw_path = NULL; |
21 | |
22 | static void die(char *fmt, ...) |
23 | { |
24 | va_list ap; |
25 | |
26 | va_start(ap, fmt); |
27 | vfprintf(stderr, fmt, ap); |
28 | va_end(ap); |
29 | if (fw_path) |
30 | unlink(fw_path); |
31 | umount("/lib/firmware" ); |
32 | exit(EXIT_FAILURE); |
33 | } |
34 | |
35 | static void trigger_fw(const char *fw_name, const char *sys_path) |
36 | { |
37 | int fd; |
38 | |
39 | fd = open(sys_path, O_WRONLY); |
40 | if (fd < 0) |
41 | die(fmt: "open failed: %s\n" , |
42 | strerror(errno)); |
43 | if (write(fd, fw_name, strlen(fw_name)) != strlen(fw_name)) |
44 | exit(EXIT_FAILURE); |
45 | close(fd); |
46 | } |
47 | |
48 | static void setup_fw(const char *fw_path) |
49 | { |
50 | int fd; |
51 | const char fw[] = "ABCD0123" ; |
52 | |
53 | fd = open(fw_path, O_WRONLY | O_CREAT, 0600); |
54 | if (fd < 0) |
55 | die(fmt: "open failed: %s\n" , |
56 | strerror(errno)); |
57 | if (write(fd, fw, sizeof(fw) -1) != sizeof(fw) -1) |
58 | die(fmt: "write failed: %s\n" , |
59 | strerror(errno)); |
60 | close(fd); |
61 | } |
62 | |
63 | static bool test_fw_in_ns(const char *fw_name, const char *sys_path, bool block_fw_in_parent_ns) |
64 | { |
65 | pid_t child; |
66 | |
67 | if (block_fw_in_parent_ns) |
68 | if (mount("test" , "/lib/firmware" , "tmpfs" , MS_RDONLY, NULL) == -1) |
69 | die(fmt: "blocking firmware in parent ns failed\n" ); |
70 | |
71 | child = fork(); |
72 | if (child == -1) { |
73 | die(fmt: "fork failed: %s\n" , |
74 | strerror(errno)); |
75 | } |
76 | if (child != 0) { /* parent */ |
77 | pid_t pid; |
78 | int status; |
79 | |
80 | pid = waitpid(child, &status, 0); |
81 | if (pid == -1) { |
82 | die(fmt: "waitpid failed: %s\n" , |
83 | strerror(errno)); |
84 | } |
85 | if (pid != child) { |
86 | die("waited for %d got %d\n" , |
87 | child, pid); |
88 | } |
89 | if (!WIFEXITED(status)) { |
90 | die(fmt: "child did not terminate cleanly\n" ); |
91 | } |
92 | if (block_fw_in_parent_ns) |
93 | umount("/lib/firmware" ); |
94 | return WEXITSTATUS(status) == EXIT_SUCCESS; |
95 | } |
96 | |
97 | if (unshare(CLONE_NEWNS) != 0) { |
98 | die(fmt: "unshare(CLONE_NEWNS) failed: %s\n" , |
99 | strerror(errno)); |
100 | } |
101 | if (mount(NULL, "/" , NULL, MS_SLAVE|MS_REC, NULL) == -1) |
102 | die(fmt: "remount root in child ns failed\n" ); |
103 | |
104 | if (!block_fw_in_parent_ns) { |
105 | if (mount("test" , "/lib/firmware" , "tmpfs" , MS_RDONLY, NULL) == -1) |
106 | die(fmt: "blocking firmware in child ns failed\n" ); |
107 | } else |
108 | umount("/lib/firmware" ); |
109 | |
110 | trigger_fw(fw_name, sys_path); |
111 | |
112 | exit(EXIT_SUCCESS); |
113 | } |
114 | |
115 | int main(int argc, char **argv) |
116 | { |
117 | const char *fw_name = "test-firmware.bin" ; |
118 | char *sys_path; |
119 | if (argc != 2) |
120 | die(fmt: "usage: %s sys_path\n" , argv[0]); |
121 | |
122 | /* Mount tmpfs to /lib/firmware so we don't have to assume |
123 | that it is writable for us.*/ |
124 | if (mount("test" , "/lib/firmware" , "tmpfs" , 0, NULL) == -1) |
125 | die(fmt: "mounting tmpfs to /lib/firmware failed\n" ); |
126 | |
127 | sys_path = argv[1]; |
128 | if (asprintf(&fw_path, "/lib/firmware/%s" , fw_name) < 0) |
129 | die(fmt: "error: failed to build full fw_path\n" ); |
130 | |
131 | setup_fw(fw_path); |
132 | |
133 | setvbuf(stdout, NULL, _IONBF, 0); |
134 | /* Positive case: firmware in PID1 mount namespace */ |
135 | printf("Testing with firmware in parent namespace (assumed to be same file system as PID1)\n" ); |
136 | if (!test_fw_in_ns(fw_name, sys_path, false)) |
137 | die(fmt: "error: failed to access firmware\n" ); |
138 | |
139 | /* Negative case: firmware in child mount namespace, expected to fail */ |
140 | printf("Testing with firmware in child namespace\n" ); |
141 | if (test_fw_in_ns(fw_name, sys_path, true)) |
142 | die(fmt: "error: firmware access did not fail\n" ); |
143 | |
144 | unlink(fw_path); |
145 | free(fw_path); |
146 | umount("/lib/firmware" ); |
147 | exit(EXIT_SUCCESS); |
148 | } |
149 | |