History

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.

Oleg Nesterov committed 14 years ago

Commit 413cd3d1 parent 4d1d61a4 files · +46 −45Browse files

Unified · Split

4 files
  1. include/linux/key.h +2 −4
  2. security/keys/internal.h +2 −0
  3. security/keys/keyctl.c +35 −28
  4. security/keys/process_keys.c +7 −13
include/linux/key.h+2−4
@@ -33,6 +33,8 @@ typedef uint32_t key_perm_t; · 32 unmodified lines
3333
3434 struct key;
3535
36+#define key_replace_session_keyring() do { } while (0)
37+
3638 #ifdef CONFIG_KEYS
3739
3840 #undef KEY_DEBUGGING
@@ -308,9 +310,6 @@ static inline bool key_is_instantiated(const struct key *key) · 269 unmodified lines
308310 #ifdef CONFIG_SYSCTL
309311 extern ctl_table key_sysctls[];
310312 #endif
311−
312−extern void key_replace_session_keyring(void);
313−
314313 /*
315314 * the userspace interface
316315 */
@@ -334,7 +333,6 @@ extern void key_init(void); · 17 unmodified lines
334333 #define key_fsuid_changed(t) do { } while(0)
335334 #define key_fsgid_changed(t) do { } while(0)
336335 #define key_init() do { } while(0)
337−#define key_replace_session_keyring() do { } while(0)
338336
339337 #endif /* CONFIG_KEYS */
340338 #endif /* __KERNEL__ */
security/keys/internal.h+2−0
@@ -14,6 +14,7 @@ · 13 unmodified lines
1414
1515 #include <linux/sched.h>
1616 #include <linux/key-type.h>
17+#include <linux/task_work.h>
1718
1819 #ifdef __KDEBUG
1920 #define kenter(FMT, ...) \
@@ -148,6 +149,7 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, · 128 unmodified lines
148149 #define KEY_LOOKUP_FOR_UNLINK 0x04
149150
150151 extern long join_session_keyring(const char *name);
152+extern void key_change_session_keyring(struct task_work *twork);
151153
152154 extern struct work_struct key_gc_work;
153155 extern unsigned key_gc_delay;
security/keys/keyctl.c+35−28
@@ -1456,47 +1456,55 @@ long keyctl_session_to_parent(void) · 1455 unmodified lines
14561456 {
14571457 struct task_struct *me, *parent;
14581458 const struct cred *mycred, *pcred;
1459− struct cred *cred, *oldcred;
1459+ struct task_work *newwork, *oldwork;
14601460 key_ref_t keyring_r;
1461+ struct cred *cred;
14611462 int ret;
14621463
14631464 keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
14641465 if (IS_ERR(keyring_r))
14651466 return PTR_ERR(keyring_r);
14661467
1468+ ret = -ENOMEM;
1469+ newwork = kmalloc(sizeof(struct task_work), GFP_KERNEL);
1470+ if (!newwork)
1471+ goto error_keyring;
1472+
14671473 /* our parent is going to need a new cred struct, a new tgcred struct
14681474 * and new security data, so we allocate them here to prevent ENOMEM in
14691475 * our parent */
1470− ret = -ENOMEM;
14711476 cred = cred_alloc_blank();
14721477 if (!cred)
1473− goto error_keyring;
1478+ goto error_newwork;
14741479
14751480 cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
1476− keyring_r = NULL;
1481+ init_task_work(newwork, key_change_session_keyring, cred);
14771482
14781483 me = current;
14791484 rcu_read_lock();
14801485 write_lock_irq(&tasklist_lock);
14811486
1482− parent = me->real_parent;
14831487 ret = -EPERM;
1488+ oldwork = NULL;
1489+ parent = me->real_parent;
14841490
14851491 /* the parent mustn't be init and mustn't be a kernel thread */
14861492 if (parent->pid <= 1 || !parent->mm)
1487− goto not_permitted;
1493+ goto unlock;
14881494
14891495 /* the parent must be single threaded */
14901496 if (!thread_group_empty(parent))
1491− goto not_permitted;
1497+ goto unlock;
14921498
14931499 /* the parent and the child must have different session keyrings or
14941500 * there's no point */
14951501 mycred = current_cred();
14961502 pcred = __task_cred(parent);
14971503 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+ }
15001508
15011509 /* the parent must have the same effective ownership and mustn't be
15021510 * SUID/SGID */
@@ -1506,38 +1514,37 @@ long keyctl_session_to_parent(void) · 3 unmodified lines
15061514 pcred->gid != mycred->egid ||
15071515 pcred->egid != mycred->egid ||
15081516 pcred->sgid != mycred->egid)
1509− goto not_permitted;
1517+ goto unlock;
15101518
15111519 /* the keyrings must have the same UID */
15121520 if ((pcred->tgcred->session_keyring &&
15131521 pcred->tgcred->session_keyring->uid != mycred->euid) ||
15141522 mycred->tgcred->session_keyring->uid != mycred->euid)
1515− goto not_permitted;
1523+ goto unlock;
15161524
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);
15201527
15211528 /* the replacement session keyring is applied just prior to userspace
15221529 * restarting */
1523− parent->replacement_session_keyring = cred;
1524− cred = NULL;
1525− set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
1526−
1527− write_unlock_irq(&tasklist_lock);
1528− rcu_read_unlock();
1529− if (oldcred)
1530− put_cred(oldcred);
1531− return 0;
1532−
1533−already_same:
1534− ret = 0;
1535−not_permitted:
1530+ ret = task_work_add(parent, newwork, true);
1531+ if (!ret)
1532+ newwork = NULL;
1533+unlock:
15361534 write_unlock_irq(&tasklist_lock);
15371535 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+ }
15391544 return ret;
15401545
1546+error_newwork:
1547+ kfree(newwork);
15411548 error_keyring:
15421549 key_ref_put(keyring_r);
15431550 return ret;
security/keys/process_keys.c+7−13
@@ -834,23 +834,17 @@ error: · 833 unmodified lines
834834 * Replace a process's session keyring on behalf of one of its children when
835835 * the target process is about to resume userspace execution.
836836 */
837−void key_replace_session_keyring(void)
837+void key_change_session_keyring(struct task_work *twork)
838838 {
839− const struct cred *old;
840− struct cred *new;
841−
842− if (!current->replacement_session_keyring)
843− return;
839+ const struct cred *old = current_cred();
840+ struct cred *new = twork->data;
844841
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)
842+ kfree(twork);
843+ if (unlikely(current->flags & PF_EXITING)) {
844+ put_cred(new);
851845 return;
846+ }
852847
853− old = current_cred();
854848 new-> uid = old-> uid;
855849 new-> euid = old-> euid;
856850 new-> suid = old-> suid;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t