Skip to content

Commit 3a50597

Browse files
committed
KEYS: Make the session and process keyrings per-thread
Make the session keyring per-thread rather than per-process, but still inherited from the parent thread to solve a problem with PAM and gdm. The problem is that join_session_keyring() will reject attempts to change the session keyring of a multithreaded program but gdm is now multithreaded before it gets to the point of starting PAM and running pam_keyinit to create the session keyring. See: https://bugs.freedesktop.org/show_bug.cgi?id=49211 The reason that join_session_keyring() will only change the session keyring under a single-threaded environment is that it's hard to alter the other thread's credentials to effect the change in a multi-threaded program. The problems are such as: (1) How to prevent two threads both running join_session_keyring() from racing. (2) Another thread's credentials may not be modified directly by this process. (3) The number of threads is uncertain whilst we're not holding the appropriate spinlock, making preallocation slightly tricky. (4) We could use TIF_NOTIFY_RESUME and key_replace_session_keyring() to get another thread to replace its keyring, but that means preallocating for each thread. A reasonable way around this is to make the session keyring per-thread rather than per-process and just document that if you want a common session keyring, you must get it before you spawn any threads - which is the current situation anyway. Whilst we're at it, we can the process keyring behave in the same way. This means we can clean up some of the ickyness in the creds code. Basically, after this patch, the session, process and thread keyrings are about inheritance rules only and not about sharing changes of keyring. Reported-by: Mantas M. <grawity@gmail.com> Signed-off-by: David Howells <dhowells@redhat.com> Tested-by: Ray Strode <rstrode@redhat.com>
1 parent a84a921 commit 3a50597

File tree

5 files changed

+50
-181
lines changed

5 files changed

+50
-181
lines changed

include/linux/cred.h

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,6 @@ extern int groups_search(const struct group_info *, kgid_t);
7676
extern int in_group_p(kgid_t);
7777
extern int in_egroup_p(kgid_t);
7878

79-
/*
80-
* The common credentials for a thread group
81-
* - shared by CLONE_THREAD
82-
*/
83-
#ifdef CONFIG_KEYS
84-
struct thread_group_cred {
85-
atomic_t usage;
86-
pid_t tgid; /* thread group process ID */
87-
spinlock_t lock;
88-
struct key __rcu *session_keyring; /* keyring inherited over fork */
89-
struct key *process_keyring; /* keyring private to this process */
90-
struct rcu_head rcu; /* RCU deletion hook */
91-
};
92-
#endif
93-
9479
/*
9580
* The security context of a task
9681
*
@@ -139,6 +124,8 @@ struct cred {
139124
#ifdef CONFIG_KEYS
140125
unsigned char jit_keyring; /* default keyring to attach requested
141126
* keys to */
127+
struct key __rcu *session_keyring; /* keyring inherited over fork */
128+
struct key *process_keyring; /* keyring private to this process */
142129
struct key *thread_keyring; /* keyring private to this thread */
143130
struct key *request_key_auth; /* assumed request_key authority */
144131
struct thread_group_cred *tgcred; /* thread-group shared credentials */

kernel/cred.c

Lines changed: 15 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,6 @@
2929

3030
static struct kmem_cache *cred_jar;
3131

32-
/*
33-
* The common credentials for the initial task's thread group
34-
*/
35-
#ifdef CONFIG_KEYS
36-
static struct thread_group_cred init_tgcred = {
37-
.usage = ATOMIC_INIT(2),
38-
.tgid = 0,
39-
.lock = __SPIN_LOCK_UNLOCKED(init_cred.tgcred.lock),
40-
};
41-
#endif
42-
4332
/*
4433
* The initial credentials for the initial task
4534
*/
@@ -65,9 +54,6 @@ struct cred init_cred = {
6554
.user = INIT_USER,
6655
.user_ns = &init_user_ns,
6756
.group_info = &init_groups,
68-
#ifdef CONFIG_KEYS
69-
.tgcred = &init_tgcred,
70-
#endif
7157
};
7258

7359
static inline void set_cred_subscribers(struct cred *cred, int n)
@@ -95,36 +81,6 @@ static inline void alter_cred_subscribers(const struct cred *_cred, int n)
9581
#endif
9682
}
9783

98-
/*
99-
* Dispose of the shared task group credentials
100-
*/
101-
#ifdef CONFIG_KEYS
102-
static void release_tgcred_rcu(struct rcu_head *rcu)
103-
{
104-
struct thread_group_cred *tgcred =
105-
container_of(rcu, struct thread_group_cred, rcu);
106-
107-
BUG_ON(atomic_read(&tgcred->usage) != 0);
108-
109-
key_put(tgcred->session_keyring);
110-
key_put(tgcred->process_keyring);
111-
kfree(tgcred);
112-
}
113-
#endif
114-
115-
/*
116-
* Release a set of thread group credentials.
117-
*/
118-
static void release_tgcred(struct cred *cred)
119-
{
120-
#ifdef CONFIG_KEYS
121-
struct thread_group_cred *tgcred = cred->tgcred;
122-
123-
if (atomic_dec_and_test(&tgcred->usage))
124-
call_rcu(&tgcred->rcu, release_tgcred_rcu);
125-
#endif
126-
}
127-
12884
/*
12985
* The RCU callback to actually dispose of a set of credentials
13086
*/
@@ -150,9 +106,10 @@ static void put_cred_rcu(struct rcu_head *rcu)
150106
#endif
151107

152108
security_cred_free(cred);
109+
key_put(cred->session_keyring);
110+
key_put(cred->process_keyring);
153111
key_put(cred->thread_keyring);
154112
key_put(cred->request_key_auth);
155-
release_tgcred(cred);
156113
if (cred->group_info)
157114
put_group_info(cred->group_info);
158115
free_uid(cred->user);
@@ -246,15 +203,6 @@ struct cred *cred_alloc_blank(void)
246203
if (!new)
247204
return NULL;
248205

249-
#ifdef CONFIG_KEYS
250-
new->tgcred = kzalloc(sizeof(*new->tgcred), GFP_KERNEL);
251-
if (!new->tgcred) {
252-
kmem_cache_free(cred_jar, new);
253-
return NULL;
254-
}
255-
atomic_set(&new->tgcred->usage, 1);
256-
#endif
257-
258206
atomic_set(&new->usage, 1);
259207
#ifdef CONFIG_DEBUG_CREDENTIALS
260208
new->magic = CRED_MAGIC;
@@ -308,9 +256,10 @@ struct cred *prepare_creds(void)
308256
get_user_ns(new->user_ns);
309257

310258
#ifdef CONFIG_KEYS
259+
key_get(new->session_keyring);
260+
key_get(new->process_keyring);
311261
key_get(new->thread_keyring);
312262
key_get(new->request_key_auth);
313-
atomic_inc(&new->tgcred->usage);
314263
#endif
315264

316265
#ifdef CONFIG_SECURITY
@@ -334,39 +283,20 @@ EXPORT_SYMBOL(prepare_creds);
334283
*/
335284
struct cred *prepare_exec_creds(void)
336285
{
337-
struct thread_group_cred *tgcred = NULL;
338286
struct cred *new;
339287

340-
#ifdef CONFIG_KEYS
341-
tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
342-
if (!tgcred)
343-
return NULL;
344-
#endif
345-
346288
new = prepare_creds();
347-
if (!new) {
348-
kfree(tgcred);
289+
if (!new)
349290
return new;
350-
}
351291

352292
#ifdef CONFIG_KEYS
353293
/* newly exec'd tasks don't get a thread keyring */
354294
key_put(new->thread_keyring);
355295
new->thread_keyring = NULL;
356296

357-
/* create a new per-thread-group creds for all this set of threads to
358-
* share */
359-
memcpy(tgcred, new->tgcred, sizeof(struct thread_group_cred));
360-
361-
atomic_set(&tgcred->usage, 1);
362-
spin_lock_init(&tgcred->lock);
363-
364297
/* inherit the session keyring; new process keyring */
365-
key_get(tgcred->session_keyring);
366-
tgcred->process_keyring = NULL;
367-
368-
release_tgcred(new);
369-
new->tgcred = tgcred;
298+
key_put(new->process_keyring);
299+
new->process_keyring = NULL;
370300
#endif
371301

372302
return new;
@@ -383,9 +313,6 @@ struct cred *prepare_exec_creds(void)
383313
*/
384314
int copy_creds(struct task_struct *p, unsigned long clone_flags)
385315
{
386-
#ifdef CONFIG_KEYS
387-
struct thread_group_cred *tgcred;
388-
#endif
389316
struct cred *new;
390317
int ret;
391318

@@ -425,22 +352,12 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)
425352
install_thread_keyring_to_cred(new);
426353
}
427354

428-
/* we share the process and session keyrings between all the threads in
429-
* a process - this is slightly icky as we violate COW credentials a
430-
* bit */
355+
/* The process keyring is only shared between the threads in a process;
356+
* anything outside of those threads doesn't inherit.
357+
*/
431358
if (!(clone_flags & CLONE_THREAD)) {
432-
tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
433-
if (!tgcred) {
434-
ret = -ENOMEM;
435-
goto error_put;
436-
}
437-
atomic_set(&tgcred->usage, 1);
438-
spin_lock_init(&tgcred->lock);
439-
tgcred->process_keyring = NULL;
440-
tgcred->session_keyring = key_get(new->tgcred->session_keyring);
441-
442-
release_tgcred(new);
443-
new->tgcred = tgcred;
359+
key_put(new->process_keyring);
360+
new->process_keyring = NULL;
444361
}
445362
#endif
446363

@@ -643,24 +560,13 @@ void __init cred_init(void)
643560
*/
644561
struct cred *prepare_kernel_cred(struct task_struct *daemon)
645562
{
646-
#ifdef CONFIG_KEYS
647-
struct thread_group_cred *tgcred;
648-
#endif
649563
const struct cred *old;
650564
struct cred *new;
651565

652566
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
653567
if (!new)
654568
return NULL;
655569

656-
#ifdef CONFIG_KEYS
657-
tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
658-
if (!tgcred) {
659-
kmem_cache_free(cred_jar, new);
660-
return NULL;
661-
}
662-
#endif
663-
664570
kdebug("prepare_kernel_cred() alloc %p", new);
665571

666572
if (daemon)
@@ -678,13 +584,10 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)
678584
get_group_info(new->group_info);
679585

680586
#ifdef CONFIG_KEYS
681-
atomic_set(&tgcred->usage, 1);
682-
spin_lock_init(&tgcred->lock);
683-
tgcred->process_keyring = NULL;
684-
tgcred->session_keyring = NULL;
685-
new->tgcred = tgcred;
686-
new->request_key_auth = NULL;
587+
new->session_keyring = NULL;
588+
new->process_keyring = NULL;
687589
new->thread_keyring = NULL;
590+
new->request_key_auth = NULL;
688591
new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
689592
#endif
690593

security/keys/keyctl.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,7 +1475,8 @@ long keyctl_session_to_parent(void)
14751475
goto error_keyring;
14761476
newwork = &cred->rcu;
14771477

1478-
cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
1478+
cred->session_keyring = key_ref_to_ptr(keyring_r);
1479+
keyring_r = NULL;
14791480
init_task_work(newwork, key_change_session_keyring);
14801481

14811482
me = current;
@@ -1500,7 +1501,7 @@ long keyctl_session_to_parent(void)
15001501
mycred = current_cred();
15011502
pcred = __task_cred(parent);
15021503
if (mycred == pcred ||
1503-
mycred->tgcred->session_keyring == pcred->tgcred->session_keyring) {
1504+
mycred->session_keyring == pcred->session_keyring) {
15041505
ret = 0;
15051506
goto unlock;
15061507
}
@@ -1516,9 +1517,9 @@ long keyctl_session_to_parent(void)
15161517
goto unlock;
15171518

15181519
/* the keyrings must have the same UID */
1519-
if ((pcred->tgcred->session_keyring &&
1520-
pcred->tgcred->session_keyring->uid != mycred->euid) ||
1521-
mycred->tgcred->session_keyring->uid != mycred->euid)
1520+
if ((pcred->session_keyring &&
1521+
pcred->session_keyring->uid != mycred->euid) ||
1522+
mycred->session_keyring->uid != mycred->euid)
15221523
goto unlock;
15231524

15241525
/* cancel an already pending keyring replacement */

0 commit comments

Comments
 (0)