1 /*
2 * pam_keyinit: Initialise the session keyring on login through a PAM module
3 *
4 * Copyright (C) 2006 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 License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 */
12
13 #include "config.h"
14 #include <stdarg.h>
15 #include <string.h>
16 #include <syslog.h>
17 #include <pwd.h>
18 #include <unistd.h>
19 #include <errno.h>
20 #include <security/pam_modules.h>
21 #include <security/pam_modutil.h>
22 #include <security/pam_ext.h>
23 #include <sys/syscall.h>
24 #include <stdatomic.h>
25
26 #define KEY_SPEC_SESSION_KEYRING -3 /* ID for session keyring */
27 #define KEY_SPEC_USER_KEYRING -4 /* ID for UID-specific keyring */
28 #define KEY_SPEC_USER_SESSION_KEYRING -5 /* - key ID for UID-session keyring */
29
30 #define KEYCTL_GET_KEYRING_ID 0 /* ask for a keyring's ID */
31 #define KEYCTL_JOIN_SESSION_KEYRING 1 /* start named session keyring */
32 #define KEYCTL_REVOKE 3 /* revoke a key */
33 #define KEYCTL_LINK 8 /* link a key into a keyring */
34
35 static _Thread_local int my_session_keyring = 0;
36 static _Atomic int session_counter = 0;
37 static _Thread_local int do_revoke = 0;
38 static _Thread_local uid_t revoke_as_uid;
39 static _Thread_local gid_t revoke_as_gid;
40 static _Thread_local int xdebug = 0;
41
42 static void debug(pam_handle_t *pamh, const char *fmt, ...)
43 __attribute__((format(printf, 2, 3)));
44
45 static void debug(pam_handle_t *pamh, const char *fmt, ...)
46 {
47 va_list va;
48
49 if (xdebug) {
50 va_start(va, fmt);
51 pam_vsyslog(pamh, LOG_DEBUG, fmt, va);
52 va_end(va);
53 }
54 }
55
56 static void error(pam_handle_t *pamh, const char *fmt, ...)
57 __attribute__((format(printf, 2, 3)));
58
59 static void error(pam_handle_t *pamh, const char *fmt, ...)
60 {
61 va_list va;
62
63 va_start(va, fmt);
64 pam_vsyslog(pamh, LOG_ERR, fmt, va);
65 va_end(va);
66 }
67
68 static int pam_setreuid(uid_t ruid, uid_t euid)
69 {
70 #if defined(SYS_setreuid32)
71 return syscall(SYS_setreuid32, ruid, euid);
72 #else
73 return syscall(SYS_setreuid, ruid, euid);
74 #endif
75 }
76
77 static int pam_setregid(gid_t rgid, gid_t egid)
78 {
79 #if defined(SYS_setregid32)
80 return syscall(SYS_setregid32, rgid, egid);
81 #else
82 return syscall(SYS_setregid, rgid, egid);
83 #endif
84 }
85
86 static int pam_setresuid(uid_t ruid, uid_t euid, uid_t suid)
87 {
88 #if defined(SYS_setresuid32)
89 return syscall(SYS_setresuid32, ruid, euid, suid);
90 #else
91 return syscall(SYS_setresuid, ruid, euid, suid);
92 #endif
93 }
94
95 /*
96 * initialise the session keyring for this process
97 */
98 static int init_keyrings(pam_handle_t *pamh, int force, int error_ret)
99 {
100 int session, usession, ret;
101
102 if (!force) {
103 /* get the IDs of the session keyring and the user session
104 * keyring */
105 session = syscall(__NR_keyctl,
106 KEYCTL_GET_KEYRING_ID,
107 KEY_SPEC_SESSION_KEYRING,
108 0);
109 debug(pamh, "GET SESSION = %d", session);
110 if (session < 0) {
111 /* don't worry about keyrings if facility not
112 * installed */
113 if (errno == ENOSYS)
114 return PAM_SUCCESS;
115 return error_ret;
116 }
117
118 usession = syscall(__NR_keyctl,
119 KEYCTL_GET_KEYRING_ID,
120 KEY_SPEC_USER_SESSION_KEYRING,
121 0);
122 debug(pamh, "GET SESSION = %d", usession);
123 if (usession < 0)
124 return error_ret;
125
126 /* if the user session keyring is our keyring, then we don't
127 * need to do anything if we're not forcing */
128 if (session != usession)
129 return PAM_SUCCESS;
130 }
131
132 /* create a session keyring, discarding the old one */
133 ret = syscall(__NR_keyctl,
134 KEYCTL_JOIN_SESSION_KEYRING,
135 NULL);
136 debug(pamh, "JOIN = %d", ret);
137 if (ret < 0)
138 return error_ret;
139
140 my_session_keyring = ret;
141
142 /* make a link from the session keyring to the user keyring */
143 ret = syscall(__NR_keyctl,
144 KEYCTL_LINK,
145 KEY_SPEC_USER_KEYRING,
146 KEY_SPEC_SESSION_KEYRING);
147
148 return ret < 0 ? error_ret : PAM_SUCCESS;
149 }
150
151 /*
152 * revoke the session keyring for this process
153 */
154 static int kill_keyrings(pam_handle_t *pamh, int error_ret)
155 {
156 uid_t old_uid;
157 gid_t old_gid;
158 int ret = PAM_SUCCESS;
159
160 /* revoke the session keyring we created earlier */
161 if (my_session_keyring > 0) {
162 debug(pamh, "REVOKE %d", my_session_keyring);
163
164 old_uid = geteuid();
165 old_gid = getegid();
166 debug(pamh, "UID:%d [%d] GID:%d [%d]",
167 revoke_as_uid, old_uid, revoke_as_gid, old_gid);
168
169 /* switch to the real UID and GID so that we have permission to
170 * revoke the key */
171 if (revoke_as_gid != old_gid && pam_setregid(-1, revoke_as_gid) < 0) {
172 error(pamh, "Unable to change GID to %d temporarily\n", revoke_as_gid);
173 return error_ret;
174 }
175
176 if (revoke_as_uid != old_uid && pam_setresuid(-1, revoke_as_uid, old_uid) < 0) {
177 error(pamh, "Unable to change UID to %d temporarily\n", revoke_as_uid);
178 if (getegid() != old_gid && pam_setregid(-1, old_gid) < 0)
179 error(pamh, "Unable to change GID back to %d\n", old_gid);
180 return error_ret;
181 }
182
183 if (syscall(__NR_keyctl, KEYCTL_REVOKE, my_session_keyring) < 0) {
184 ret = error_ret;
185 }
186
187 /* return to the original UID and GID (probably root) */
188 if (revoke_as_uid != old_uid && pam_setreuid(-1, old_uid) < 0) {
189 error(pamh, "Unable to change UID back to %d\n", old_uid);
190 ret = error_ret;
191 }
192
193 if (revoke_as_gid != old_gid && pam_setregid(-1, old_gid) < 0) {
194 error(pamh, "Unable to change GID back to %d\n", old_gid);
195 ret = error_ret;
196 }
197
198 my_session_keyring = 0;
199 }
200 return ret;
201 }
202
203 static int do_keyinit(pam_handle_t *pamh, int argc, const char **argv, int error_ret)
204 {
205 struct passwd *pw;
206 const char *username;
207 int ret, loop, force = 0;
208 uid_t old_uid, uid;
209 gid_t old_gid, gid;
210
211 for (loop = 0; loop < argc; loop++) {
212 if (strcmp(argv[loop], "force") == 0)
213 force = 1;
214 else if (strcmp(argv[loop], "debug") == 0)
215 xdebug = 1;
216 else if (strcmp(argv[loop], "revoke") == 0)
217 do_revoke = 1;
218 }
219
220 /* don't do anything if already created a keyring (will be called
221 * multiple times if mentioned more than once in a pam script)
222 */
223 if (my_session_keyring > 0)
224 return PAM_SUCCESS;
225
226 /* look up the target UID */
227 ret = pam_get_user(pamh, &username, "key user");
228 if (ret != PAM_SUCCESS)
229 return ret;
230
231 pw = pam_modutil_getpwnam(pamh, username);
232 if (!pw) {
233 pam_syslog(pamh, LOG_NOTICE, "Unable to look up user \"%s\"\n",
234 username);
235 return PAM_USER_UNKNOWN;
236 }
237
238 revoke_as_uid = uid = pw->pw_uid;
239 old_uid = getuid();
240 revoke_as_gid = gid = pw->pw_gid;
241 old_gid = getgid();
242 debug(pamh, "UID:%d [%d] GID:%d [%d]", uid, old_uid, gid, old_gid);
243
244 /* switch to the real UID and GID so that the keyring ends up owned by
245 * the right user */
246 if (gid != old_gid && pam_setregid(gid, -1) < 0) {
247 error(pamh, "Unable to change GID to %d temporarily\n", gid);
248 return error_ret;
249 }
250
251 if (uid != old_uid && pam_setreuid(uid, -1) < 0) {
252 error(pamh, "Unable to change UID to %d temporarily\n", uid);
253 if (pam_setregid(old_gid, -1) < 0)
254 error(pamh, "Unable to change GID back to %d\n", old_gid);
255 return error_ret;
256 }
257
258 ret = init_keyrings(pamh, force, error_ret);
259
260 /* return to the original UID and GID (probably root) */
261 if (uid != old_uid && pam_setreuid(old_uid, -1) < 0) {
262 error(pamh, "Unable to change UID back to %d\n", old_uid);
263 ret = error_ret;
264 }
265
266 if (gid != old_gid && pam_setregid(old_gid, -1) < 0) {
267 error(pamh, "Unable to change GID back to %d\n", old_gid);
268 ret = error_ret;
269 }
270
271 return ret;
272 }
273
274 /*
275 * Dummy
276 */
277 int pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED,
278 int argc UNUSED, const char **argv UNUSED)
279 {
280 return PAM_IGNORE;
281 }
282
283 /*
284 * since setcred and open_session are called in different orders, a
285 * session ring is invoked by the first of these functions called.
286 */
287 int pam_sm_setcred(pam_handle_t *pamh, int flags,
288 int argc, const char **argv)
289 {
290 if (flags & PAM_ESTABLISH_CRED) {
291 debug(pamh, "ESTABLISH_CRED");
292 return do_keyinit(pamh, argc, argv, PAM_CRED_ERR);
293 }
294 if (flags & PAM_DELETE_CRED && my_session_keyring > 0 && do_revoke) {
295 debug(pamh, "DELETE_CRED");
296 return kill_keyrings(pamh, PAM_CRED_ERR);
297 }
298 return PAM_IGNORE;
299 }
300
301 int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
302 int argc, const char **argv)
303 {
304 session_counter++;
305
306 debug(pamh, "OPEN %d", session_counter);
307
308 return do_keyinit(pamh, argc, argv, PAM_SESSION_ERR);
309 }
310
311 /*
312 * close a PAM session by revoking the session keyring if requested
313 */
314 int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
315 int argc UNUSED, const char **argv UNUSED)
316 {
317 debug(pamh, "CLOSE %d,%d,%d",
318 session_counter, my_session_keyring, do_revoke);
319
320 session_counter--;
321
322 if (session_counter <= 0 && my_session_keyring > 0 && do_revoke)
323 kill_keyrings(pamh, PAM_SESSION_ERR);
324
325 return PAM_SUCCESS;
326 }