18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Module and Firmware Pinning Security Module 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2011-2016 Google Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Kees Cook <keescook@chromium.org> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "LoadPin: " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/fs.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel_read_file.h> 158c2ecf20Sopenharmony_ci#include <linux/lsm_hooks.h> 168c2ecf20Sopenharmony_ci#include <linux/mount.h> 178c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 188c2ecf20Sopenharmony_ci#include <linux/path.h> 198c2ecf20Sopenharmony_ci#include <linux/sched.h> /* current */ 208c2ecf20Sopenharmony_ci#include <linux/string_helpers.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic void report_load(const char *origin, struct file *file, char *operation) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci char *cmdline, *pathname; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci pathname = kstrdup_quotable_file(file, GFP_KERNEL); 278c2ecf20Sopenharmony_ci cmdline = kstrdup_quotable_cmdline(current, GFP_KERNEL); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci pr_notice("%s %s obj=%s%s%s pid=%d cmdline=%s%s%s\n", 308c2ecf20Sopenharmony_ci origin, operation, 318c2ecf20Sopenharmony_ci (pathname && pathname[0] != '<') ? "\"" : "", 328c2ecf20Sopenharmony_ci pathname, 338c2ecf20Sopenharmony_ci (pathname && pathname[0] != '<') ? "\"" : "", 348c2ecf20Sopenharmony_ci task_pid_nr(current), 358c2ecf20Sopenharmony_ci cmdline ? "\"" : "", cmdline, cmdline ? "\"" : ""); 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci kfree(cmdline); 388c2ecf20Sopenharmony_ci kfree(pathname); 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic int enforce = IS_ENABLED(CONFIG_SECURITY_LOADPIN_ENFORCE); 428c2ecf20Sopenharmony_cistatic char *exclude_read_files[READING_MAX_ID]; 438c2ecf20Sopenharmony_cistatic int ignore_read_file_id[READING_MAX_ID] __ro_after_init; 448c2ecf20Sopenharmony_cistatic struct super_block *pinned_root; 458c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(pinned_root_spinlock); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#ifdef CONFIG_SYSCTL 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic struct ctl_path loadpin_sysctl_path[] = { 508c2ecf20Sopenharmony_ci { .procname = "kernel", }, 518c2ecf20Sopenharmony_ci { .procname = "loadpin", }, 528c2ecf20Sopenharmony_ci { } 538c2ecf20Sopenharmony_ci}; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic struct ctl_table loadpin_sysctl_table[] = { 568c2ecf20Sopenharmony_ci { 578c2ecf20Sopenharmony_ci .procname = "enforce", 588c2ecf20Sopenharmony_ci .data = &enforce, 598c2ecf20Sopenharmony_ci .maxlen = sizeof(int), 608c2ecf20Sopenharmony_ci .mode = 0644, 618c2ecf20Sopenharmony_ci .proc_handler = proc_dointvec_minmax, 628c2ecf20Sopenharmony_ci .extra1 = SYSCTL_ZERO, 638c2ecf20Sopenharmony_ci .extra2 = SYSCTL_ONE, 648c2ecf20Sopenharmony_ci }, 658c2ecf20Sopenharmony_ci { } 668c2ecf20Sopenharmony_ci}; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* 698c2ecf20Sopenharmony_ci * This must be called after early kernel init, since then the rootdev 708c2ecf20Sopenharmony_ci * is available. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_cistatic void check_pinning_enforcement(struct super_block *mnt_sb) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci bool ro = false; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci /* 778c2ecf20Sopenharmony_ci * If load pinning is not enforced via a read-only block 788c2ecf20Sopenharmony_ci * device, allow sysctl to change modes for testing. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci if (mnt_sb->s_bdev) { 818c2ecf20Sopenharmony_ci char bdev[BDEVNAME_SIZE]; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci ro = bdev_read_only(mnt_sb->s_bdev); 848c2ecf20Sopenharmony_ci bdevname(mnt_sb->s_bdev, bdev); 858c2ecf20Sopenharmony_ci pr_info("%s (%u:%u): %s\n", bdev, 868c2ecf20Sopenharmony_ci MAJOR(mnt_sb->s_bdev->bd_dev), 878c2ecf20Sopenharmony_ci MINOR(mnt_sb->s_bdev->bd_dev), 888c2ecf20Sopenharmony_ci ro ? "read-only" : "writable"); 898c2ecf20Sopenharmony_ci } else 908c2ecf20Sopenharmony_ci pr_info("mnt_sb lacks block device, treating as: writable\n"); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci if (!ro) { 938c2ecf20Sopenharmony_ci if (!register_sysctl_paths(loadpin_sysctl_path, 948c2ecf20Sopenharmony_ci loadpin_sysctl_table)) 958c2ecf20Sopenharmony_ci pr_notice("sysctl registration failed!\n"); 968c2ecf20Sopenharmony_ci else 978c2ecf20Sopenharmony_ci pr_info("enforcement can be disabled.\n"); 988c2ecf20Sopenharmony_ci } else 998c2ecf20Sopenharmony_ci pr_info("load pinning engaged.\n"); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci#else 1028c2ecf20Sopenharmony_cistatic void check_pinning_enforcement(struct super_block *mnt_sb) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci pr_info("load pinning engaged.\n"); 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci#endif 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void loadpin_sb_free_security(struct super_block *mnt_sb) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci /* 1118c2ecf20Sopenharmony_ci * When unmounting the filesystem we were using for load 1128c2ecf20Sopenharmony_ci * pinning, we acknowledge the superblock release, but make sure 1138c2ecf20Sopenharmony_ci * no other modules or firmware can be loaded. 1148c2ecf20Sopenharmony_ci */ 1158c2ecf20Sopenharmony_ci if (!IS_ERR_OR_NULL(pinned_root) && mnt_sb == pinned_root) { 1168c2ecf20Sopenharmony_ci pinned_root = ERR_PTR(-EIO); 1178c2ecf20Sopenharmony_ci pr_info("umount pinned fs: refusing further loads\n"); 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int loadpin_check(struct file *file, enum kernel_read_file_id id) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct super_block *load_root; 1248c2ecf20Sopenharmony_ci const char *origin = kernel_read_file_id_str(id); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* If the file id is excluded, ignore the pinning. */ 1278c2ecf20Sopenharmony_ci if ((unsigned int)id < ARRAY_SIZE(ignore_read_file_id) && 1288c2ecf20Sopenharmony_ci ignore_read_file_id[id]) { 1298c2ecf20Sopenharmony_ci report_load(origin, file, "pinning-excluded"); 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci /* This handles the older init_module API that has a NULL file. */ 1348c2ecf20Sopenharmony_ci if (!file) { 1358c2ecf20Sopenharmony_ci if (!enforce) { 1368c2ecf20Sopenharmony_ci report_load(origin, NULL, "old-api-pinning-ignored"); 1378c2ecf20Sopenharmony_ci return 0; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci report_load(origin, NULL, "old-api-denied"); 1418c2ecf20Sopenharmony_ci return -EPERM; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci load_root = file->f_path.mnt->mnt_sb; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci /* First loaded module/firmware defines the root for all others. */ 1478c2ecf20Sopenharmony_ci spin_lock(&pinned_root_spinlock); 1488c2ecf20Sopenharmony_ci /* 1498c2ecf20Sopenharmony_ci * pinned_root is only NULL at startup. Otherwise, it is either 1508c2ecf20Sopenharmony_ci * a valid reference, or an ERR_PTR. 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_ci if (!pinned_root) { 1538c2ecf20Sopenharmony_ci pinned_root = load_root; 1548c2ecf20Sopenharmony_ci /* 1558c2ecf20Sopenharmony_ci * Unlock now since it's only pinned_root we care about. 1568c2ecf20Sopenharmony_ci * In the worst case, we will (correctly) report pinning 1578c2ecf20Sopenharmony_ci * failures before we have announced that pinning is 1588c2ecf20Sopenharmony_ci * enforcing. This would be purely cosmetic. 1598c2ecf20Sopenharmony_ci */ 1608c2ecf20Sopenharmony_ci spin_unlock(&pinned_root_spinlock); 1618c2ecf20Sopenharmony_ci check_pinning_enforcement(pinned_root); 1628c2ecf20Sopenharmony_ci report_load(origin, file, "pinned"); 1638c2ecf20Sopenharmony_ci } else { 1648c2ecf20Sopenharmony_ci spin_unlock(&pinned_root_spinlock); 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci if (IS_ERR_OR_NULL(pinned_root) || load_root != pinned_root) { 1688c2ecf20Sopenharmony_ci if (unlikely(!enforce)) { 1698c2ecf20Sopenharmony_ci report_load(origin, file, "pinning-ignored"); 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci report_load(origin, file, "denied"); 1748c2ecf20Sopenharmony_ci return -EPERM; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci return 0; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic int loadpin_read_file(struct file *file, enum kernel_read_file_id id, 1818c2ecf20Sopenharmony_ci bool contents) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci /* 1848c2ecf20Sopenharmony_ci * LoadPin only cares about the _origin_ of a file, not its 1858c2ecf20Sopenharmony_ci * contents, so we can ignore the "are full contents available" 1868c2ecf20Sopenharmony_ci * argument here. 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ci return loadpin_check(file, id); 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic int loadpin_load_data(enum kernel_load_data_id id, bool contents) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci /* 1948c2ecf20Sopenharmony_ci * LoadPin only cares about the _origin_ of a file, not its 1958c2ecf20Sopenharmony_ci * contents, so a NULL file is passed, and we can ignore the 1968c2ecf20Sopenharmony_ci * state of "contents". 1978c2ecf20Sopenharmony_ci */ 1988c2ecf20Sopenharmony_ci return loadpin_check(NULL, (enum kernel_read_file_id) id); 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic struct security_hook_list loadpin_hooks[] __lsm_ro_after_init = { 2028c2ecf20Sopenharmony_ci LSM_HOOK_INIT(sb_free_security, loadpin_sb_free_security), 2038c2ecf20Sopenharmony_ci LSM_HOOK_INIT(kernel_read_file, loadpin_read_file), 2048c2ecf20Sopenharmony_ci LSM_HOOK_INIT(kernel_load_data, loadpin_load_data), 2058c2ecf20Sopenharmony_ci}; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic void __init parse_exclude(void) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci int i, j; 2108c2ecf20Sopenharmony_ci char *cur; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci /* 2138c2ecf20Sopenharmony_ci * Make sure all the arrays stay within expected sizes. This 2148c2ecf20Sopenharmony_ci * is slightly weird because kernel_read_file_str[] includes 2158c2ecf20Sopenharmony_ci * READING_MAX_ID, which isn't actually meaningful here. 2168c2ecf20Sopenharmony_ci */ 2178c2ecf20Sopenharmony_ci BUILD_BUG_ON(ARRAY_SIZE(exclude_read_files) != 2188c2ecf20Sopenharmony_ci ARRAY_SIZE(ignore_read_file_id)); 2198c2ecf20Sopenharmony_ci BUILD_BUG_ON(ARRAY_SIZE(kernel_read_file_str) < 2208c2ecf20Sopenharmony_ci ARRAY_SIZE(ignore_read_file_id)); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(exclude_read_files); i++) { 2238c2ecf20Sopenharmony_ci cur = exclude_read_files[i]; 2248c2ecf20Sopenharmony_ci if (!cur) 2258c2ecf20Sopenharmony_ci break; 2268c2ecf20Sopenharmony_ci if (*cur == '\0') 2278c2ecf20Sopenharmony_ci continue; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci for (j = 0; j < ARRAY_SIZE(ignore_read_file_id); j++) { 2308c2ecf20Sopenharmony_ci if (strcmp(cur, kernel_read_file_str[j]) == 0) { 2318c2ecf20Sopenharmony_ci pr_info("excluding: %s\n", 2328c2ecf20Sopenharmony_ci kernel_read_file_str[j]); 2338c2ecf20Sopenharmony_ci ignore_read_file_id[j] = 1; 2348c2ecf20Sopenharmony_ci /* 2358c2ecf20Sopenharmony_ci * Can not break, because one read_file_str 2368c2ecf20Sopenharmony_ci * may map to more than on read_file_id. 2378c2ecf20Sopenharmony_ci */ 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic int __init loadpin_init(void) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci pr_info("ready to pin (currently %senforcing)\n", 2468c2ecf20Sopenharmony_ci enforce ? "" : "not "); 2478c2ecf20Sopenharmony_ci parse_exclude(); 2488c2ecf20Sopenharmony_ci security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin"); 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ciDEFINE_LSM(loadpin) = { 2538c2ecf20Sopenharmony_ci .name = "loadpin", 2548c2ecf20Sopenharmony_ci .init = loadpin_init, 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci/* Should not be mutable after boot, so not listed in sysfs (perm == 0). */ 2588c2ecf20Sopenharmony_cimodule_param(enforce, int, 0); 2598c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enforce, "Enforce module/firmware pinning"); 2608c2ecf20Sopenharmony_cimodule_param_array_named(exclude, exclude_read_files, charp, NULL, 0); 2618c2ecf20Sopenharmony_ciMODULE_PARM_DESC(exclude, "Exclude pinning specific read file types"); 262