|
13 | 13 | #include <linux/lsm_hooks.h> |
14 | 14 | #include <linux/rcupdate.h> |
15 | 15 | #include <linux/sched.h> |
| 16 | +#include <net/af_unix.h> |
| 17 | +#include <net/sock.h> |
16 | 18 |
|
17 | 19 | #include "common.h" |
18 | 20 | #include "cred.h" |
@@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent) |
108 | 110 | return task_ptrace(parent, current); |
109 | 111 | } |
110 | 112 |
|
| 113 | +/** |
| 114 | + * domain_is_scoped - Checks if the client domain is scoped in the same |
| 115 | + * domain as the server. |
| 116 | + * |
| 117 | + * @client: IPC sender domain. |
| 118 | + * @server: IPC receiver domain. |
| 119 | + * @scope: The scope restriction criteria. |
| 120 | + * |
| 121 | + * Returns: True if the @client domain is scoped to access the @server, |
| 122 | + * unless the @server is also scoped in the same domain as @client. |
| 123 | + */ |
| 124 | +static bool domain_is_scoped(const struct landlock_ruleset *const client, |
| 125 | + const struct landlock_ruleset *const server, |
| 126 | + access_mask_t scope) |
| 127 | +{ |
| 128 | + int client_layer, server_layer; |
| 129 | + struct landlock_hierarchy *client_walker, *server_walker; |
| 130 | + |
| 131 | + /* Quick return if client has no domain */ |
| 132 | + if (WARN_ON_ONCE(!client)) |
| 133 | + return false; |
| 134 | + |
| 135 | + client_layer = client->num_layers - 1; |
| 136 | + client_walker = client->hierarchy; |
| 137 | + /* |
| 138 | + * client_layer must be a signed integer with greater capacity |
| 139 | + * than client->num_layers to ensure the following loop stops. |
| 140 | + */ |
| 141 | + BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); |
| 142 | + |
| 143 | + server_layer = server ? (server->num_layers - 1) : -1; |
| 144 | + server_walker = server ? server->hierarchy : NULL; |
| 145 | + |
| 146 | + /* |
| 147 | + * Walks client's parent domains down to the same hierarchy level |
| 148 | + * as the server's domain, and checks that none of these client's |
| 149 | + * parent domains are scoped. |
| 150 | + */ |
| 151 | + for (; client_layer > server_layer; client_layer--) { |
| 152 | + if (landlock_get_scope_mask(client, client_layer) & scope) |
| 153 | + return true; |
| 154 | + |
| 155 | + client_walker = client_walker->parent; |
| 156 | + } |
| 157 | + /* |
| 158 | + * Walks server's parent domains down to the same hierarchy level as |
| 159 | + * the client's domain. |
| 160 | + */ |
| 161 | + for (; server_layer > client_layer; server_layer--) |
| 162 | + server_walker = server_walker->parent; |
| 163 | + |
| 164 | + for (; client_layer >= 0; client_layer--) { |
| 165 | + if (landlock_get_scope_mask(client, client_layer) & scope) { |
| 166 | + /* |
| 167 | + * Client and server are at the same level in the |
| 168 | + * hierarchy. If the client is scoped, the request is |
| 169 | + * only allowed if this domain is also a server's |
| 170 | + * ancestor. |
| 171 | + */ |
| 172 | + return server_walker != client_walker; |
| 173 | + } |
| 174 | + client_walker = client_walker->parent; |
| 175 | + server_walker = server_walker->parent; |
| 176 | + } |
| 177 | + return false; |
| 178 | +} |
| 179 | + |
| 180 | +static bool sock_is_scoped(struct sock *const other, |
| 181 | + const struct landlock_ruleset *const domain) |
| 182 | +{ |
| 183 | + const struct landlock_ruleset *dom_other; |
| 184 | + |
| 185 | + /* The credentials will not change. */ |
| 186 | + lockdep_assert_held(&unix_sk(other)->lock); |
| 187 | + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; |
| 188 | + return domain_is_scoped(domain, dom_other, |
| 189 | + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); |
| 190 | +} |
| 191 | + |
| 192 | +static bool is_abstract_socket(struct sock *const sock) |
| 193 | +{ |
| 194 | + struct unix_address *addr = unix_sk(sock)->addr; |
| 195 | + |
| 196 | + if (!addr) |
| 197 | + return false; |
| 198 | + |
| 199 | + if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 && |
| 200 | + addr->name->sun_path[0] == '\0') |
| 201 | + return true; |
| 202 | + |
| 203 | + return false; |
| 204 | +} |
| 205 | + |
| 206 | +static int hook_unix_stream_connect(struct sock *const sock, |
| 207 | + struct sock *const other, |
| 208 | + struct sock *const newsk) |
| 209 | +{ |
| 210 | + const struct landlock_ruleset *const dom = |
| 211 | + landlock_get_current_domain(); |
| 212 | + |
| 213 | + /* Quick return for non-landlocked tasks. */ |
| 214 | + if (!dom) |
| 215 | + return 0; |
| 216 | + |
| 217 | + if (is_abstract_socket(other) && sock_is_scoped(other, dom)) |
| 218 | + return -EPERM; |
| 219 | + |
| 220 | + return 0; |
| 221 | +} |
| 222 | + |
| 223 | +static int hook_unix_may_send(struct socket *const sock, |
| 224 | + struct socket *const other) |
| 225 | +{ |
| 226 | + const struct landlock_ruleset *const dom = |
| 227 | + landlock_get_current_domain(); |
| 228 | + |
| 229 | + if (!dom) |
| 230 | + return 0; |
| 231 | + |
| 232 | + /* |
| 233 | + * Checks if this datagram socket was already allowed to be connected |
| 234 | + * to other. |
| 235 | + */ |
| 236 | + if (unix_peer(sock->sk) == other->sk) |
| 237 | + return 0; |
| 238 | + |
| 239 | + if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) |
| 240 | + return -EPERM; |
| 241 | + |
| 242 | + return 0; |
| 243 | +} |
| 244 | + |
111 | 245 | static struct security_hook_list landlock_hooks[] __ro_after_init = { |
112 | 246 | LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), |
113 | 247 | LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), |
| 248 | + |
| 249 | + LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), |
| 250 | + LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), |
114 | 251 | }; |
115 | 252 |
|
116 | 253 | __init void landlock_add_task_hooks(void) |
|
0 commit comments