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