1000d388eSMatthew Garrett // SPDX-License-Identifier: GPL-2.0
2000d388eSMatthew Garrett /* Lock down the kernel
3000d388eSMatthew Garrett *
4000d388eSMatthew Garrett * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved.
5000d388eSMatthew Garrett * Written by David Howells (dhowells@redhat.com)
6000d388eSMatthew Garrett *
7000d388eSMatthew Garrett * This program is free software; you can redistribute it and/or
8000d388eSMatthew Garrett * modify it under the terms of the GNU General Public Licence
9000d388eSMatthew Garrett * as published by the Free Software Foundation; either version
10000d388eSMatthew Garrett * 2 of the Licence, or (at your option) any later version.
11000d388eSMatthew Garrett */
12000d388eSMatthew Garrett
13000d388eSMatthew Garrett #include <linux/security.h>
14000d388eSMatthew Garrett #include <linux/export.h>
15000d388eSMatthew Garrett #include <linux/lsm_hooks.h>
16000d388eSMatthew Garrett
17000d388eSMatthew Garrett static enum lockdown_reason kernel_locked_down;
18000d388eSMatthew Garrett
19f8a9bc62SMatthew Garrett static const enum lockdown_reason lockdown_levels[] = {LOCKDOWN_NONE,
20000d388eSMatthew Garrett LOCKDOWN_INTEGRITY_MAX,
21000d388eSMatthew Garrett LOCKDOWN_CONFIDENTIALITY_MAX};
22000d388eSMatthew Garrett
23000d388eSMatthew Garrett /*
24000d388eSMatthew Garrett * Put the kernel into lock-down mode.
25000d388eSMatthew Garrett */
lock_kernel_down(const char * where,enum lockdown_reason level)26000d388eSMatthew Garrett static int lock_kernel_down(const char *where, enum lockdown_reason level)
27000d388eSMatthew Garrett {
28000d388eSMatthew Garrett if (kernel_locked_down >= level)
29000d388eSMatthew Garrett return -EPERM;
30000d388eSMatthew Garrett
31000d388eSMatthew Garrett kernel_locked_down = level;
32000d388eSMatthew Garrett pr_notice("Kernel is locked down from %s; see man kernel_lockdown.7\n",
33000d388eSMatthew Garrett where);
34000d388eSMatthew Garrett return 0;
35000d388eSMatthew Garrett }
36000d388eSMatthew Garrett
lockdown_param(char * level)37000d388eSMatthew Garrett static int __init lockdown_param(char *level)
38000d388eSMatthew Garrett {
39000d388eSMatthew Garrett if (!level)
40000d388eSMatthew Garrett return -EINVAL;
41000d388eSMatthew Garrett
42000d388eSMatthew Garrett if (strcmp(level, "integrity") == 0)
43000d388eSMatthew Garrett lock_kernel_down("command line", LOCKDOWN_INTEGRITY_MAX);
44000d388eSMatthew Garrett else if (strcmp(level, "confidentiality") == 0)
45000d388eSMatthew Garrett lock_kernel_down("command line", LOCKDOWN_CONFIDENTIALITY_MAX);
46000d388eSMatthew Garrett else
47000d388eSMatthew Garrett return -EINVAL;
48000d388eSMatthew Garrett
49000d388eSMatthew Garrett return 0;
50000d388eSMatthew Garrett }
51000d388eSMatthew Garrett
52000d388eSMatthew Garrett early_param("lockdown", lockdown_param);
53000d388eSMatthew Garrett
54000d388eSMatthew Garrett /**
55000d388eSMatthew Garrett * lockdown_is_locked_down - Find out if the kernel is locked down
56000d388eSMatthew Garrett * @what: Tag to use in notice generated if lockdown is in effect
57000d388eSMatthew Garrett */
lockdown_is_locked_down(enum lockdown_reason what)58000d388eSMatthew Garrett static int lockdown_is_locked_down(enum lockdown_reason what)
59000d388eSMatthew Garrett {
60b602614aSMatthew Garrett if (WARN(what >= LOCKDOWN_CONFIDENTIALITY_MAX,
61b602614aSMatthew Garrett "Invalid lockdown reason"))
62b602614aSMatthew Garrett return -EPERM;
63b602614aSMatthew Garrett
64000d388eSMatthew Garrett if (kernel_locked_down >= what) {
65000d388eSMatthew Garrett if (lockdown_reasons[what])
661e7d8bcbSNathan Lynch pr_notice_ratelimited("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
67b602614aSMatthew Garrett current->comm, lockdown_reasons[what]);
68000d388eSMatthew Garrett return -EPERM;
69000d388eSMatthew Garrett }
70000d388eSMatthew Garrett
71000d388eSMatthew Garrett return 0;
72000d388eSMatthew Garrett }
73000d388eSMatthew Garrett
74*f22f9aafSPaul Moore static struct security_hook_list lockdown_hooks[] __ro_after_init = {
75000d388eSMatthew Garrett LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
76000d388eSMatthew Garrett };
77000d388eSMatthew Garrett
lockdown_lsm_init(void)78000d388eSMatthew Garrett static int __init lockdown_lsm_init(void)
79000d388eSMatthew Garrett {
80000d388eSMatthew Garrett #if defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY)
81000d388eSMatthew Garrett lock_kernel_down("Kernel configuration", LOCKDOWN_INTEGRITY_MAX);
82000d388eSMatthew Garrett #elif defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY)
83000d388eSMatthew Garrett lock_kernel_down("Kernel configuration", LOCKDOWN_CONFIDENTIALITY_MAX);
84000d388eSMatthew Garrett #endif
85000d388eSMatthew Garrett security_add_hooks(lockdown_hooks, ARRAY_SIZE(lockdown_hooks),
86000d388eSMatthew Garrett "lockdown");
87000d388eSMatthew Garrett return 0;
88000d388eSMatthew Garrett }
89000d388eSMatthew Garrett
lockdown_read(struct file * filp,char __user * buf,size_t count,loff_t * ppos)90000d388eSMatthew Garrett static ssize_t lockdown_read(struct file *filp, char __user *buf, size_t count,
91000d388eSMatthew Garrett loff_t *ppos)
92000d388eSMatthew Garrett {
93000d388eSMatthew Garrett char temp[80];
94000d388eSMatthew Garrett int i, offset = 0;
95000d388eSMatthew Garrett
96000d388eSMatthew Garrett for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) {
97000d388eSMatthew Garrett enum lockdown_reason level = lockdown_levels[i];
98000d388eSMatthew Garrett
99000d388eSMatthew Garrett if (lockdown_reasons[level]) {
100000d388eSMatthew Garrett const char *label = lockdown_reasons[level];
101000d388eSMatthew Garrett
102000d388eSMatthew Garrett if (kernel_locked_down == level)
103000d388eSMatthew Garrett offset += sprintf(temp+offset, "[%s] ", label);
104000d388eSMatthew Garrett else
105000d388eSMatthew Garrett offset += sprintf(temp+offset, "%s ", label);
106000d388eSMatthew Garrett }
107000d388eSMatthew Garrett }
108000d388eSMatthew Garrett
109000d388eSMatthew Garrett /* Convert the last space to a newline if needed. */
110000d388eSMatthew Garrett if (offset > 0)
111000d388eSMatthew Garrett temp[offset-1] = '\n';
112000d388eSMatthew Garrett
113000d388eSMatthew Garrett return simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
114000d388eSMatthew Garrett }
115000d388eSMatthew Garrett
lockdown_write(struct file * file,const char __user * buf,size_t n,loff_t * ppos)116000d388eSMatthew Garrett static ssize_t lockdown_write(struct file *file, const char __user *buf,
117000d388eSMatthew Garrett size_t n, loff_t *ppos)
118000d388eSMatthew Garrett {
119000d388eSMatthew Garrett char *state;
120000d388eSMatthew Garrett int i, len, err = -EINVAL;
121000d388eSMatthew Garrett
122000d388eSMatthew Garrett state = memdup_user_nul(buf, n);
123000d388eSMatthew Garrett if (IS_ERR(state))
124000d388eSMatthew Garrett return PTR_ERR(state);
125000d388eSMatthew Garrett
126000d388eSMatthew Garrett len = strlen(state);
127000d388eSMatthew Garrett if (len && state[len-1] == '\n') {
128000d388eSMatthew Garrett state[len-1] = '\0';
129000d388eSMatthew Garrett len--;
130000d388eSMatthew Garrett }
131000d388eSMatthew Garrett
132000d388eSMatthew Garrett for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) {
133000d388eSMatthew Garrett enum lockdown_reason level = lockdown_levels[i];
134000d388eSMatthew Garrett const char *label = lockdown_reasons[level];
135000d388eSMatthew Garrett
136000d388eSMatthew Garrett if (label && !strcmp(state, label))
137000d388eSMatthew Garrett err = lock_kernel_down("securityfs", level);
138000d388eSMatthew Garrett }
139000d388eSMatthew Garrett
140000d388eSMatthew Garrett kfree(state);
141000d388eSMatthew Garrett return err ? err : n;
142000d388eSMatthew Garrett }
143000d388eSMatthew Garrett
144000d388eSMatthew Garrett static const struct file_operations lockdown_ops = {
145000d388eSMatthew Garrett .read = lockdown_read,
146000d388eSMatthew Garrett .write = lockdown_write,
147000d388eSMatthew Garrett };
148000d388eSMatthew Garrett
lockdown_secfs_init(void)149000d388eSMatthew Garrett static int __init lockdown_secfs_init(void)
150000d388eSMatthew Garrett {
151000d388eSMatthew Garrett struct dentry *dentry;
152000d388eSMatthew Garrett
15360cf7c5eSJeremy Cline dentry = securityfs_create_file("lockdown", 0644, NULL, NULL,
154000d388eSMatthew Garrett &lockdown_ops);
155000d388eSMatthew Garrett return PTR_ERR_OR_ZERO(dentry);
156000d388eSMatthew Garrett }
157000d388eSMatthew Garrett
158000d388eSMatthew Garrett core_initcall(lockdown_secfs_init);
159000d388eSMatthew Garrett
160000d388eSMatthew Garrett #ifdef CONFIG_SECURITY_LOCKDOWN_LSM_EARLY
161000d388eSMatthew Garrett DEFINE_EARLY_LSM(lockdown) = {
162000d388eSMatthew Garrett #else
163000d388eSMatthew Garrett DEFINE_LSM(lockdown) = {
164000d388eSMatthew Garrett #endif
165000d388eSMatthew Garrett .name = "lockdown",
166000d388eSMatthew Garrett .init = lockdown_lsm_init,
167000d388eSMatthew Garrett };
168