1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Lock down the kernel |
3 | * |
4 | * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved. |
5 | * Written by David Howells (dhowells@redhat.com) |
6 | * |
7 | * This program is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU General Public Licence |
9 | * as published by the Free Software Foundation; either version |
10 | * 2 of the Licence, or (at your option) any later version. |
11 | */ |
12 | |
13 | #include <linux/security.h> |
14 | #include <linux/export.h> |
15 | #include <linux/lsm_hooks.h> |
16 | #include <uapi/linux/lsm.h> |
17 | |
18 | static enum lockdown_reason kernel_locked_down; |
19 | |
20 | static const enum lockdown_reason lockdown_levels[] = {LOCKDOWN_NONE, |
21 | LOCKDOWN_INTEGRITY_MAX, |
22 | LOCKDOWN_CONFIDENTIALITY_MAX}; |
23 | |
24 | /* |
25 | * Put the kernel into lock-down mode. |
26 | */ |
27 | static int lock_kernel_down(const char *where, enum lockdown_reason level) |
28 | { |
29 | if (kernel_locked_down >= level) |
30 | return -EPERM; |
31 | |
32 | kernel_locked_down = level; |
33 | pr_notice("Kernel is locked down from %s; see man kernel_lockdown.7\n" , |
34 | where); |
35 | return 0; |
36 | } |
37 | |
38 | static int __init lockdown_param(char *level) |
39 | { |
40 | if (!level) |
41 | return -EINVAL; |
42 | |
43 | if (strcmp(level, "integrity" ) == 0) |
44 | lock_kernel_down(where: "command line" , level: LOCKDOWN_INTEGRITY_MAX); |
45 | else if (strcmp(level, "confidentiality" ) == 0) |
46 | lock_kernel_down(where: "command line" , level: LOCKDOWN_CONFIDENTIALITY_MAX); |
47 | else |
48 | return -EINVAL; |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | early_param("lockdown" , lockdown_param); |
54 | |
55 | /** |
56 | * lockdown_is_locked_down - Find out if the kernel is locked down |
57 | * @what: Tag to use in notice generated if lockdown is in effect |
58 | */ |
59 | static int lockdown_is_locked_down(enum lockdown_reason what) |
60 | { |
61 | if (WARN(what >= LOCKDOWN_CONFIDENTIALITY_MAX, |
62 | "Invalid lockdown reason" )) |
63 | return -EPERM; |
64 | |
65 | if (kernel_locked_down >= what) { |
66 | if (lockdown_reasons[what]) |
67 | pr_notice_ratelimited("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n" , |
68 | current->comm, lockdown_reasons[what]); |
69 | return -EPERM; |
70 | } |
71 | |
72 | return 0; |
73 | } |
74 | |
75 | static struct security_hook_list lockdown_hooks[] __ro_after_init = { |
76 | LSM_HOOK_INIT(locked_down, lockdown_is_locked_down), |
77 | }; |
78 | |
79 | const struct lsm_id lockdown_lsmid = { |
80 | .name = "lockdown" , |
81 | .id = LSM_ID_LOCKDOWN, |
82 | }; |
83 | |
84 | static int __init lockdown_lsm_init(void) |
85 | { |
86 | #if defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY) |
87 | lock_kernel_down("Kernel configuration" , LOCKDOWN_INTEGRITY_MAX); |
88 | #elif defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY) |
89 | lock_kernel_down("Kernel configuration" , LOCKDOWN_CONFIDENTIALITY_MAX); |
90 | #endif |
91 | security_add_hooks(hooks: lockdown_hooks, ARRAY_SIZE(lockdown_hooks), |
92 | lsmid: &lockdown_lsmid); |
93 | return 0; |
94 | } |
95 | |
96 | static ssize_t lockdown_read(struct file *filp, char __user *buf, size_t count, |
97 | loff_t *ppos) |
98 | { |
99 | char temp[80]; |
100 | int i, offset = 0; |
101 | |
102 | for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) { |
103 | enum lockdown_reason level = lockdown_levels[i]; |
104 | |
105 | if (lockdown_reasons[level]) { |
106 | const char *label = lockdown_reasons[level]; |
107 | |
108 | if (kernel_locked_down == level) |
109 | offset += sprintf(buf: temp+offset, fmt: "[%s] " , label); |
110 | else |
111 | offset += sprintf(buf: temp+offset, fmt: "%s " , label); |
112 | } |
113 | } |
114 | |
115 | /* Convert the last space to a newline if needed. */ |
116 | if (offset > 0) |
117 | temp[offset-1] = '\n'; |
118 | |
119 | return simple_read_from_buffer(to: buf, count, ppos, from: temp, strlen(temp)); |
120 | } |
121 | |
122 | static ssize_t lockdown_write(struct file *file, const char __user *buf, |
123 | size_t n, loff_t *ppos) |
124 | { |
125 | char *state; |
126 | int i, len, err = -EINVAL; |
127 | |
128 | state = memdup_user_nul(buf, n); |
129 | if (IS_ERR(ptr: state)) |
130 | return PTR_ERR(ptr: state); |
131 | |
132 | len = strlen(state); |
133 | if (len && state[len-1] == '\n') { |
134 | state[len-1] = '\0'; |
135 | len--; |
136 | } |
137 | |
138 | for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) { |
139 | enum lockdown_reason level = lockdown_levels[i]; |
140 | const char *label = lockdown_reasons[level]; |
141 | |
142 | if (label && !strcmp(state, label)) |
143 | err = lock_kernel_down(where: "securityfs" , level); |
144 | } |
145 | |
146 | kfree(objp: state); |
147 | return err ? err : n; |
148 | } |
149 | |
150 | static const struct file_operations lockdown_ops = { |
151 | .read = lockdown_read, |
152 | .write = lockdown_write, |
153 | }; |
154 | |
155 | static int __init lockdown_secfs_init(void) |
156 | { |
157 | struct dentry *dentry; |
158 | |
159 | dentry = securityfs_create_file(name: "lockdown" , mode: 0644, NULL, NULL, |
160 | fops: &lockdown_ops); |
161 | return PTR_ERR_OR_ZERO(ptr: dentry); |
162 | } |
163 | |
164 | core_initcall(lockdown_secfs_init); |
165 | |
166 | #ifdef CONFIG_SECURITY_LOCKDOWN_LSM_EARLY |
167 | DEFINE_EARLY_LSM(lockdown) = { |
168 | #else |
169 | DEFINE_LSM(lockdown) = { |
170 | #endif |
171 | .name = "lockdown" , |
172 | .init = lockdown_lsm_init, |
173 | }; |
174 | |