162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Test triggering of loading of firmware from different mount 362306a36Sopenharmony_ci * namespaces. Expect firmware to be always loaded from the mount 462306a36Sopenharmony_ci * namespace of PID 1. */ 562306a36Sopenharmony_ci#define _GNU_SOURCE 662306a36Sopenharmony_ci#include <errno.h> 762306a36Sopenharmony_ci#include <fcntl.h> 862306a36Sopenharmony_ci#include <sched.h> 962306a36Sopenharmony_ci#include <stdarg.h> 1062306a36Sopenharmony_ci#include <stdbool.h> 1162306a36Sopenharmony_ci#include <stdio.h> 1262306a36Sopenharmony_ci#include <stdlib.h> 1362306a36Sopenharmony_ci#include <string.h> 1462306a36Sopenharmony_ci#include <sys/mount.h> 1562306a36Sopenharmony_ci#include <sys/stat.h> 1662306a36Sopenharmony_ci#include <sys/types.h> 1762306a36Sopenharmony_ci#include <sys/wait.h> 1862306a36Sopenharmony_ci#include <unistd.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#ifndef CLONE_NEWNS 2162306a36Sopenharmony_ci# define CLONE_NEWNS 0x00020000 2262306a36Sopenharmony_ci#endif 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic char *fw_path = NULL; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic void die(char *fmt, ...) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci va_list ap; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci va_start(ap, fmt); 3162306a36Sopenharmony_ci vfprintf(stderr, fmt, ap); 3262306a36Sopenharmony_ci va_end(ap); 3362306a36Sopenharmony_ci if (fw_path) 3462306a36Sopenharmony_ci unlink(fw_path); 3562306a36Sopenharmony_ci umount("/lib/firmware"); 3662306a36Sopenharmony_ci exit(EXIT_FAILURE); 3762306a36Sopenharmony_ci} 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic void trigger_fw(const char *fw_name, const char *sys_path) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci int fd; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci fd = open(sys_path, O_WRONLY); 4462306a36Sopenharmony_ci if (fd < 0) 4562306a36Sopenharmony_ci die("open failed: %s\n", 4662306a36Sopenharmony_ci strerror(errno)); 4762306a36Sopenharmony_ci if (write(fd, fw_name, strlen(fw_name)) != strlen(fw_name)) 4862306a36Sopenharmony_ci exit(EXIT_FAILURE); 4962306a36Sopenharmony_ci close(fd); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic void setup_fw(const char *fw_path) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci int fd; 5562306a36Sopenharmony_ci const char fw[] = "ABCD0123"; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci fd = open(fw_path, O_WRONLY | O_CREAT, 0600); 5862306a36Sopenharmony_ci if (fd < 0) 5962306a36Sopenharmony_ci die("open failed: %s\n", 6062306a36Sopenharmony_ci strerror(errno)); 6162306a36Sopenharmony_ci if (write(fd, fw, sizeof(fw) -1) != sizeof(fw) -1) 6262306a36Sopenharmony_ci die("write failed: %s\n", 6362306a36Sopenharmony_ci strerror(errno)); 6462306a36Sopenharmony_ci close(fd); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic bool test_fw_in_ns(const char *fw_name, const char *sys_path, bool block_fw_in_parent_ns) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci pid_t child; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (block_fw_in_parent_ns) 7262306a36Sopenharmony_ci if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1) 7362306a36Sopenharmony_ci die("blocking firmware in parent ns failed\n"); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci child = fork(); 7662306a36Sopenharmony_ci if (child == -1) { 7762306a36Sopenharmony_ci die("fork failed: %s\n", 7862306a36Sopenharmony_ci strerror(errno)); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci if (child != 0) { /* parent */ 8162306a36Sopenharmony_ci pid_t pid; 8262306a36Sopenharmony_ci int status; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci pid = waitpid(child, &status, 0); 8562306a36Sopenharmony_ci if (pid == -1) { 8662306a36Sopenharmony_ci die("waitpid failed: %s\n", 8762306a36Sopenharmony_ci strerror(errno)); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci if (pid != child) { 9062306a36Sopenharmony_ci die("waited for %d got %d\n", 9162306a36Sopenharmony_ci child, pid); 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci if (!WIFEXITED(status)) { 9462306a36Sopenharmony_ci die("child did not terminate cleanly\n"); 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci if (block_fw_in_parent_ns) 9762306a36Sopenharmony_ci umount("/lib/firmware"); 9862306a36Sopenharmony_ci return WEXITSTATUS(status) == EXIT_SUCCESS; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (unshare(CLONE_NEWNS) != 0) { 10262306a36Sopenharmony_ci die("unshare(CLONE_NEWNS) failed: %s\n", 10362306a36Sopenharmony_ci strerror(errno)); 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) == -1) 10662306a36Sopenharmony_ci die("remount root in child ns failed\n"); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (!block_fw_in_parent_ns) { 10962306a36Sopenharmony_ci if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1) 11062306a36Sopenharmony_ci die("blocking firmware in child ns failed\n"); 11162306a36Sopenharmony_ci } else 11262306a36Sopenharmony_ci umount("/lib/firmware"); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci trigger_fw(fw_name, sys_path); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci exit(EXIT_SUCCESS); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ciint main(int argc, char **argv) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci const char *fw_name = "test-firmware.bin"; 12262306a36Sopenharmony_ci char *sys_path; 12362306a36Sopenharmony_ci if (argc != 2) 12462306a36Sopenharmony_ci die("usage: %s sys_path\n", argv[0]); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Mount tmpfs to /lib/firmware so we don't have to assume 12762306a36Sopenharmony_ci that it is writable for us.*/ 12862306a36Sopenharmony_ci if (mount("test", "/lib/firmware", "tmpfs", 0, NULL) == -1) 12962306a36Sopenharmony_ci die("mounting tmpfs to /lib/firmware failed\n"); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci sys_path = argv[1]; 13262306a36Sopenharmony_ci if (asprintf(&fw_path, "/lib/firmware/%s", fw_name) < 0) 13362306a36Sopenharmony_ci die("error: failed to build full fw_path\n"); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci setup_fw(fw_path); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci setvbuf(stdout, NULL, _IONBF, 0); 13862306a36Sopenharmony_ci /* Positive case: firmware in PID1 mount namespace */ 13962306a36Sopenharmony_ci printf("Testing with firmware in parent namespace (assumed to be same file system as PID1)\n"); 14062306a36Sopenharmony_ci if (!test_fw_in_ns(fw_name, sys_path, false)) 14162306a36Sopenharmony_ci die("error: failed to access firmware\n"); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* Negative case: firmware in child mount namespace, expected to fail */ 14462306a36Sopenharmony_ci printf("Testing with firmware in child namespace\n"); 14562306a36Sopenharmony_ci if (test_fw_in_ns(fw_name, sys_path, true)) 14662306a36Sopenharmony_ci die("error: firmware access did not fail\n"); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci unlink(fw_path); 14962306a36Sopenharmony_ci free(fw_path); 15062306a36Sopenharmony_ci umount("/lib/firmware"); 15162306a36Sopenharmony_ci exit(EXIT_SUCCESS); 15262306a36Sopenharmony_ci} 153