Commit 413cd3d9abeaef590e5ce00564f7a443165db238

Authored by Oleg Nesterov
Committed by Al Viro
1 parent 4d1d61a6b2

keys: change keyctl_session_to_parent() to use task_work_add()

Change keyctl_session_to_parent() to use task_work_add() and move
key_replace_session_keyring() logic into task_work->func().

Note that we do task_work_cancel() before task_work_add() to ensure that
only one work can be pending at any time.  This is important, we must not
allow user-space to abuse the parent's ->task_works list.

The callback, replace_session_keyring(), checks PF_EXITING.  I guess this
is not really needed but looks better.

As a side effect, this fixes the (unlikely) race.  The callers of
key_replace_session_keyring() and keyctl_session_to_parent() lack the
necessary barriers, the parent can miss the request.

Now we can remove task_struct->replacement_session_keyring and related
code.

Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Acked-by: David Howells <dhowells@redhat.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Richard Kuo <rkuo@codeaurora.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Alexander Gordeev <agordeev@redhat.com>
Cc: Chris Zankel <chris@zankel.net>
Cc: David Smith <dsmith@redhat.com>
Cc: "Frank Ch. Eigler" <fche@redhat.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Larry Woodman <lwoodman@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

Showing 4 changed files with 46 additions and 45 deletions Side-by-side Diff

... ... @@ -33,6 +33,8 @@
33 33  
34 34 struct key;
35 35  
  36 +#define key_replace_session_keyring() do { } while (0)
  37 +
36 38 #ifdef CONFIG_KEYS
37 39  
38 40 #undef KEY_DEBUGGING
... ... @@ -308,9 +310,6 @@
308 310 #ifdef CONFIG_SYSCTL
309 311 extern ctl_table key_sysctls[];
310 312 #endif
311   -
312   -extern void key_replace_session_keyring(void);
313   -
314 313 /*
315 314 * the userspace interface
316 315 */
... ... @@ -334,7 +333,6 @@
334 333 #define key_fsuid_changed(t) do { } while(0)
335 334 #define key_fsgid_changed(t) do { } while(0)
336 335 #define key_init() do { } while(0)
337   -#define key_replace_session_keyring() do { } while(0)
338 336  
339 337 #endif /* CONFIG_KEYS */
340 338 #endif /* __KERNEL__ */
security/keys/internal.h
... ... @@ -14,6 +14,7 @@
14 14  
15 15 #include <linux/sched.h>
16 16 #include <linux/key-type.h>
  17 +#include <linux/task_work.h>
17 18  
18 19 #ifdef __KDEBUG
19 20 #define kenter(FMT, ...) \
... ... @@ -148,6 +149,7 @@
148 149 #define KEY_LOOKUP_FOR_UNLINK 0x04
149 150  
150 151 extern long join_session_keyring(const char *name);
  152 +extern void key_change_session_keyring(struct task_work *twork);
151 153  
152 154 extern struct work_struct key_gc_work;
153 155 extern unsigned key_gc_delay;
security/keys/keyctl.c
... ... @@ -1456,47 +1456,55 @@
1456 1456 {
1457 1457 struct task_struct *me, *parent;
1458 1458 const struct cred *mycred, *pcred;
1459   - struct cred *cred, *oldcred;
  1459 + struct task_work *newwork, *oldwork;
1460 1460 key_ref_t keyring_r;
  1461 + struct cred *cred;
1461 1462 int ret;
1462 1463  
1463 1464 keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
1464 1465 if (IS_ERR(keyring_r))
1465 1466 return PTR_ERR(keyring_r);
1466 1467  
  1468 + ret = -ENOMEM;
  1469 + newwork = kmalloc(sizeof(struct task_work), GFP_KERNEL);
  1470 + if (!newwork)
  1471 + goto error_keyring;
  1472 +
1467 1473 /* our parent is going to need a new cred struct, a new tgcred struct
1468 1474 * and new security data, so we allocate them here to prevent ENOMEM in
1469 1475 * our parent */
1470   - ret = -ENOMEM;
1471 1476 cred = cred_alloc_blank();
1472 1477 if (!cred)
1473   - goto error_keyring;
  1478 + goto error_newwork;
1474 1479  
1475 1480 cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
1476   - keyring_r = NULL;
  1481 + init_task_work(newwork, key_change_session_keyring, cred);
1477 1482  
1478 1483 me = current;
1479 1484 rcu_read_lock();
1480 1485 write_lock_irq(&tasklist_lock);
1481 1486  
1482   - parent = me->real_parent;
1483 1487 ret = -EPERM;
  1488 + oldwork = NULL;
  1489 + parent = me->real_parent;
1484 1490  
1485 1491 /* the parent mustn't be init and mustn't be a kernel thread */
1486 1492 if (parent->pid <= 1 || !parent->mm)
1487   - goto not_permitted;
  1493 + goto unlock;
1488 1494  
1489 1495 /* the parent must be single threaded */
1490 1496 if (!thread_group_empty(parent))
1491   - goto not_permitted;
  1497 + goto unlock;
1492 1498  
1493 1499 /* the parent and the child must have different session keyrings or
1494 1500 * there's no point */
1495 1501 mycred = current_cred();
1496 1502 pcred = __task_cred(parent);
1497 1503 if (mycred == pcred ||
1498   - mycred->tgcred->session_keyring == pcred->tgcred->session_keyring)
1499   - goto already_same;
  1504 + mycred->tgcred->session_keyring == pcred->tgcred->session_keyring) {
  1505 + ret = 0;
  1506 + goto unlock;
  1507 + }
1500 1508  
1501 1509 /* the parent must have the same effective ownership and mustn't be
1502 1510 * SUID/SGID */
1503 1511  
1504 1512  
1505 1513  
1506 1514  
1507 1515  
... ... @@ -1506,38 +1514,37 @@
1506 1514 pcred->gid != mycred->egid ||
1507 1515 pcred->egid != mycred->egid ||
1508 1516 pcred->sgid != mycred->egid)
1509   - goto not_permitted;
  1517 + goto unlock;
1510 1518  
1511 1519 /* the keyrings must have the same UID */
1512 1520 if ((pcred->tgcred->session_keyring &&
1513 1521 pcred->tgcred->session_keyring->uid != mycred->euid) ||
1514 1522 mycred->tgcred->session_keyring->uid != mycred->euid)
1515   - goto not_permitted;
  1523 + goto unlock;
1516 1524  
1517   - /* if there's an already pending keyring replacement, then we replace
1518   - * that */
1519   - oldcred = parent->replacement_session_keyring;
  1525 + /* cancel an already pending keyring replacement */
  1526 + oldwork = task_work_cancel(parent, key_change_session_keyring);
1520 1527  
1521 1528 /* the replacement session keyring is applied just prior to userspace
1522 1529 * restarting */
1523   - parent->replacement_session_keyring = cred;
1524   - cred = NULL;
1525   - set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
1526   -
  1530 + ret = task_work_add(parent, newwork, true);
  1531 + if (!ret)
  1532 + newwork = NULL;
  1533 +unlock:
1527 1534 write_unlock_irq(&tasklist_lock);
1528 1535 rcu_read_unlock();
1529   - if (oldcred)
1530   - put_cred(oldcred);
1531   - return 0;
1532   -
1533   -already_same:
1534   - ret = 0;
1535   -not_permitted:
1536   - write_unlock_irq(&tasklist_lock);
1537   - rcu_read_unlock();
1538   - put_cred(cred);
  1536 + if (oldwork) {
  1537 + put_cred(oldwork->data);
  1538 + kfree(oldwork);
  1539 + }
  1540 + if (newwork) {
  1541 + put_cred(newwork->data);
  1542 + kfree(newwork);
  1543 + }
1539 1544 return ret;
1540 1545  
  1546 +error_newwork:
  1547 + kfree(newwork);
1541 1548 error_keyring:
1542 1549 key_ref_put(keyring_r);
1543 1550 return ret;
security/keys/process_keys.c
... ... @@ -834,23 +834,17 @@
834 834 * Replace a process's session keyring on behalf of one of its children when
835 835 * the target process is about to resume userspace execution.
836 836 */
837   -void key_replace_session_keyring(void)
  837 +void key_change_session_keyring(struct task_work *twork)
838 838 {
839   - const struct cred *old;
840   - struct cred *new;
  839 + const struct cred *old = current_cred();
  840 + struct cred *new = twork->data;
841 841  
842   - if (!current->replacement_session_keyring)
  842 + kfree(twork);
  843 + if (unlikely(current->flags & PF_EXITING)) {
  844 + put_cred(new);
843 845 return;
  846 + }
844 847  
845   - write_lock_irq(&tasklist_lock);
846   - new = current->replacement_session_keyring;
847   - current->replacement_session_keyring = NULL;
848   - write_unlock_irq(&tasklist_lock);
849   -
850   - if (!new)
851   - return;
852   -
853   - old = current_cred();
854 848 new-> uid = old-> uid;
855 849 new-> euid = old-> euid;
856 850 new-> suid = old-> suid;