KEYS: Add a keyctl to install a process's session keyring on its parent [try #6]
authorDavid Howells <dhowells@redhat.com>
Wed, 2 Sep 2009 08:14:21 +0000 (09:14 +0100)
committerJames Morris <jmorris@namei.org>
Wed, 2 Sep 2009 11:29:22 +0000 (21:29 +1000)
Add a keyctl to install a process's session keyring onto its parent.  This
replaces the parent's session keyring.  Because the COW credential code does
not permit one process to change another process's credentials directly, the
change is deferred until userspace next starts executing again.  Normally this
will be after a wait*() syscall.

To support this, three new security hooks have been provided:
cred_alloc_blank() to allocate unset security creds, cred_transfer() to fill in
the blank security creds and key_session_to_parent() - which asks the LSM if
the process may replace its parent's session keyring.

The replacement may only happen if the process has the same ownership details
as its parent, and the process has LINK permission on the session keyring, and
the session keyring is owned by the process, and the LSM permits it.

Note that this requires alteration to each architecture's notify_resume path.
This has been done for all arches barring blackfin, m68k* and xtensa, all of
which need assembly alteration to support TIF_NOTIFY_RESUME.  This allows the
replacement to be performed at the point the parent process resumes userspace
execution.

This allows the userspace AFS pioctl emulation to fully emulate newpag() and
the VIOCSETTOK and VIOCSETTOK2 pioctls, all of which require the ability to
alter the parent process's PAG membership.  However, since kAFS doesn't use
PAGs per se, but rather dumps the keys into the session keyring, the session
keyring of the parent must be replaced if, for example, VIOCSETTOK is passed
the newpag flag.

This can be tested with the following program:

#include <stdio.h>
#include <stdlib.h>
#include <keyutils.h>

#define KEYCTL_SESSION_TO_PARENT 18

#define OSERROR(X, S) do { if ((long)(X) == -1) { perror(S); exit(1); } } while(0)

int main(int argc, char **argv)
{
key_serial_t keyring, key;
long ret;

keyring = keyctl_join_session_keyring(argv[1]);
OSERROR(keyring, "keyctl_join_session_keyring");

key = add_key("user", "a", "b", 1, keyring);
OSERROR(key, "add_key");

ret = keyctl(KEYCTL_SESSION_TO_PARENT);
OSERROR(ret, "KEYCTL_SESSION_TO_PARENT");

return 0;
}

Compiled and linked with -lkeyutils, you should see something like:

[dhowells@andromeda ~]$ keyctl show
Session Keyring
       -3 --alswrv   4043  4043  keyring: _ses
355907932 --alswrv   4043    -1   \_ keyring: _uid.4043
[dhowells@andromeda ~]$ /tmp/newpag
[dhowells@andromeda ~]$ keyctl show
Session Keyring
       -3 --alswrv   4043  4043  keyring: _ses
1055658746 --alswrv   4043  4043   \_ user: a
[dhowells@andromeda ~]$ /tmp/newpag hello
[dhowells@andromeda ~]$ keyctl show
Session Keyring
       -3 --alswrv   4043  4043  keyring: hello
340417692 --alswrv   4043  4043   \_ user: a

Where the test program creates a new session keyring, sticks a user key named
'a' into it and then installs it on its parent.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: James Morris <jmorris@namei.org>
34 files changed:
Documentation/keys.txt
arch/alpha/kernel/signal.c
arch/arm/kernel/signal.c
arch/avr32/kernel/signal.c
arch/cris/kernel/ptrace.c
arch/frv/kernel/signal.c
arch/h8300/kernel/signal.c
arch/ia64/kernel/process.c
arch/m32r/kernel/signal.c
arch/mips/kernel/signal.c
arch/mn10300/kernel/signal.c
arch/parisc/kernel/signal.c
arch/s390/kernel/signal.c
arch/sh/kernel/signal_32.c
arch/sh/kernel/signal_64.c
arch/sparc/kernel/signal_32.c
arch/sparc/kernel/signal_64.c
arch/x86/kernel/signal.c
include/linux/cred.h
include/linux/key.h
include/linux/keyctl.h
include/linux/sched.h
include/linux/security.h
kernel/cred.c
security/capability.c
security/keys/compat.c
security/keys/gc.c
security/keys/internal.h
security/keys/keyctl.c
security/keys/process_keys.c
security/security.c
security/selinux/hooks.c
security/smack/smack_lsm.c
security/tomoyo/tomoyo.c

index 203487e9b1d8a262e10584f47675847b6b9a0cef..e4dbbdb1bd961e7a7a582c3dbb3ada1008218218 100644 (file)
@@ -757,6 +757,26 @@ The keyctl syscall functions are:
      successful.
 
 
+ (*) Install the calling process's session keyring on its parent.
+
+       long keyctl(KEYCTL_SESSION_TO_PARENT);
+
+     This functions attempts to install the calling process's session keyring
+     on to the calling process's parent, replacing the parent's current session
+     keyring.
+
+     The calling process must have the same ownership as its parent, the
+     keyring must have the same ownership as the calling process, the calling
+     process must have LINK permission on the keyring and the active LSM module
+     mustn't deny permission, otherwise error EPERM will be returned.
+
+     Error ENOMEM will be returned if there was insufficient memory to complete
+     the operation, otherwise 0 will be returned to indicate success.
+
+     The keyring will be replaced next time the parent process leaves the
+     kernel and resumes executing userspace.
+
+
 ===============
 KERNEL SERVICES
 ===============
index 04e17c1f0f1b2f449ccd886ec51cc6fd2794219d..d91aaa747050ad635c7d418a7cbef3de957901e3 100644 (file)
@@ -687,5 +687,7 @@ do_notify_resume(struct pt_regs *regs, struct switch_stack *sw,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 13dec276927ad1ec611b70b928029c3ee554b7c3..ea4ad3a43c88ecc9ba2750f17e95df24bd9c7dab 100644 (file)
@@ -711,5 +711,7 @@ do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
        if (thread_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 62d242e2d033480ecdb882fac5b1e873b0f3dd56..de9f7fe2aefabbae6299ba845a7eba1aabf0cc93 100644 (file)
@@ -326,5 +326,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, struct thread_info *ti)
        if (ti->flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 4f06d7fb43ddd9a8ce445bd4c88288dade18e1f4..32e9d5ee895b856764ac1ce868f8976a89f10b1c 100644 (file)
@@ -40,5 +40,7 @@ void do_notify_resume(int canrestart, struct pt_regs *regs,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 4a7a62c6e7833ed91a9087b3762cb3d0ba24df5f..6b0a2b6fed6a9326ab8f4d28955cda0bada96b6a 100644 (file)
@@ -572,6 +572,8 @@ asmlinkage void do_notify_resume(__u32 thread_info_flags)
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(__frame);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 
 } /* end do_notify_resume() */
index 56b3ab7dbbb0f2d05afcd6b446d3255b2e1ea087..abac3ee8c52acc0c0c4626a3607b2948d82fa8d5 100644 (file)
@@ -556,5 +556,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, u32 thread_info_flags)
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 5d7c0e5b9e76f150b20269cb9ba9e2497062209a..89969e9500456e3a17212b6f20ea47d6abbc6fb3 100644 (file)
@@ -192,6 +192,8 @@ do_notify_resume_user(sigset_t *unused, struct sigscratch *scr, long in_syscall)
        if (test_thread_flag(TIF_NOTIFY_RESUME)) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(&scr->pt);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 
        /* copy user rbs to kernel rbs */
index 3220258be1884e682793ceeba1149bba6b79ad1f..f80bac17c65c535f14f0ebc27071f11aa536f834 100644 (file)
@@ -411,6 +411,8 @@ void do_notify_resume(struct pt_regs *regs, sigset_t *oldset,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 
        clear_thread_flag(TIF_IRET);
index a3d1015471de422ebaf0ed79c54f1c093e0df060..c2acf31874a4c927bdbcadcb7c16ef27036a7066 100644 (file)
@@ -704,5 +704,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, void *unused,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index feb2f2e810db7c785ea5a7562ac77dfb79666a79..a21f43bc68e269cf412e6ae3b0c0293c0dcd96c0 100644 (file)
@@ -568,5 +568,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, u32 thread_info_flags)
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(__frame);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index b3bfc4326703bc8be13f80dea00da6c5dca5c0bb..5ca1c02b805ace1ca5b52cceaad1b96ee26425f9 100644 (file)
@@ -649,5 +649,7 @@ void do_notify_resume(struct pt_regs *regs, long in_syscall)
        if (test_thread_flag(TIF_NOTIFY_RESUME)) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 062bd64e65fabe1f09641a4c3e0290ae4ff013ee..6b4fef877f9d0ccf0fd28ea654b9553ab3b70df9 100644 (file)
@@ -536,4 +536,6 @@ void do_notify_resume(struct pt_regs *regs)
 {
        clear_thread_flag(TIF_NOTIFY_RESUME);
        tracehook_notify_resume(regs);
+       if (current->replacement_session_keyring)
+               key_replace_session_keyring();
 }
index b5afbec1db59ce6fb2d74cd0cd9e0fa51b879d0f..04a21883f32730bf18013031fe342e27c50b6b99 100644 (file)
@@ -640,5 +640,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, unsigned int save_r0,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 0663a0ee6021f4ab31c2bb32fd7a24fa82cd9a6a..9e5c9b1d7e9872fe3591e8520e8b7383e96e7962 100644 (file)
@@ -772,5 +772,7 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, unsigned long thread_info
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
index 181d069a2d44bdeb160125053150b894e550598b..7ce1a1005b1da4c3b13f8b87927d060376279f2a 100644 (file)
@@ -590,6 +590,8 @@ void do_notify_resume(struct pt_regs *regs, unsigned long orig_i0,
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
 
index ec82d76dc6f2cc245ca54203dd38c35fee192ed2..647afbda7ae1f170896675dcc4603dfe5c58ec0b 100644 (file)
@@ -613,5 +613,8 @@ void do_notify_resume(struct pt_regs *regs, unsigned long orig_i0, unsigned long
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 }
+
index 4c578751e94ec08dc4e6ed69338947105e444654..81e58238c4ce642ed54ea3ce63c265427e1d6984 100644 (file)
@@ -869,6 +869,8 @@ do_notify_resume(struct pt_regs *regs, void *unused, __u32 thread_info_flags)
        if (thread_info_flags & _TIF_NOTIFY_RESUME) {
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
+               if (current->replacement_session_keyring)
+                       key_replace_session_keyring();
        }
 
 #ifdef CONFIG_X86_32
index 85439abdbc8019087f234ff08502e7ac307a8e96..24520a539c6ff18b9067b24d56dc22075c7096e2 100644 (file)
@@ -152,6 +152,7 @@ struct cred {
 extern void __put_cred(struct cred *);
 extern void exit_creds(struct task_struct *);
 extern int copy_creds(struct task_struct *, unsigned long);
+extern struct cred *cred_alloc_blank(void);
 extern struct cred *prepare_creds(void);
 extern struct cred *prepare_exec_creds(void);
 extern struct cred *prepare_usermodehelper_creds(void);
index 33e0165de1009efd1885e6d69a1e6caaae7af408..cd50dfa1d4c224de2a26e2d8ef3926d7fc74435e 100644 (file)
@@ -278,6 +278,8 @@ static inline key_serial_t key_serial(struct key *key)
 extern ctl_table key_sysctls[];
 #endif
 
+extern void key_replace_session_keyring(void);
+
 /*
  * the userspace interface
  */
@@ -300,6 +302,7 @@ extern void key_init(void);
 #define key_fsuid_changed(t)           do { } while(0)
 #define key_fsgid_changed(t)           do { } while(0)
 #define key_init()                     do { } while(0)
+#define key_replace_session_keyring()  do { } while(0)
 
 #endif /* CONFIG_KEYS */
 #endif /* __KERNEL__ */
index c0688eb7209396bf8ca3d5ba3202f6b8033ef981..bd383f1944fb2fde14bcd4ae5dbd6a92714168fb 100644 (file)
@@ -52,5 +52,6 @@
 #define KEYCTL_SET_TIMEOUT             15      /* set key timeout */
 #define KEYCTL_ASSUME_AUTHORITY                16      /* assume request_key() authorisation */
 #define KEYCTL_GET_SECURITY            17      /* get key security label */
+#define KEYCTL_SESSION_TO_PARENT       18      /* apply session keyring to parent process */
 
 #endif /*  _LINUX_KEYCTL_H */
index 5c7ce13c1696586263fdfc4d3b317a245ef979c3..9304027673b0ac6abb0daf0bc8128dee3db0fcda 100644 (file)
@@ -1292,6 +1292,7 @@ struct task_struct {
        struct mutex cred_guard_mutex;  /* guard against foreign influences on
                                         * credential calculations
                                         * (notably. ptrace) */
+       struct cred *replacement_session_keyring; /* for KEYCTL_SESSION_TO_PARENT */
 
        char comm[TASK_COMM_LEN]; /* executable name excluding path
                                     - access with [gs]et_task_comm (which lock
index 40ba39ea68cec7feb10b3a4084c474468ad4b34b..97de3fe3dd0d2c3d3c3b551838384ffc5d47a5e3 100644 (file)
@@ -653,6 +653,11 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  *     manual page for definitions of the @clone_flags.
  *     @clone_flags contains the flags indicating what should be shared.
  *     Return 0 if permission is granted.
+ * @cred_alloc_blank:
+ *     @cred points to the credentials.
+ *     @gfp indicates the atomicity of any memory allocations.
+ *     Only allocate sufficient memory and attach to @cred such that
+ *     cred_transfer() will not get ENOMEM.
  * @cred_free:
  *     @cred points to the credentials.
  *     Deallocate and clear the cred->security field in a set of credentials.
@@ -665,6 +670,10 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  *     @new points to the new credentials.
  *     @old points to the original credentials.
  *     Install a new set of credentials.
+ * @cred_transfer:
+ *     @new points to the new credentials.
+ *     @old points to the original credentials.
+ *     Transfer data from original creds to new creds
  * @kernel_act_as:
  *     Set the credentials for a kernel service to act as (subjective context).
  *     @new points to the credentials to be modified.
@@ -1103,6 +1112,13 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  *     Return the length of the string (including terminating NUL) or -ve if
  *      an error.
  *     May also return 0 (and a NULL buffer pointer) if there is no label.
+ * @key_session_to_parent:
+ *     Forcibly assign the session keyring from a process to its parent
+ *     process.
+ *     @cred: Pointer to process's credentials
+ *     @parent_cred: Pointer to parent process's credentials
+ *     @keyring: Proposed new session keyring
+ *     Return 0 if permission is granted, -ve error otherwise.
  *
  * Security hooks affecting all System V IPC operations.
  *
@@ -1498,10 +1514,12 @@ struct security_operations {
        int (*dentry_open) (struct file *file, const struct cred *cred);
 
        int (*task_create) (unsigned long clone_flags);
+       int (*cred_alloc_blank) (struct cred *cred, gfp_t gfp);
        void (*cred_free) (struct cred *cred);
        int (*cred_prepare)(struct cred *new, const struct cred *old,
                            gfp_t gfp);
        void (*cred_commit)(struct cred *new, const struct cred *old);
+       void (*cred_transfer)(struct cred *new, const struct cred *old);
        int (*kernel_act_as)(struct cred *new, u32 secid);
        int (*kernel_create_files_as)(struct cred *new, struct inode *inode);
        int (*kernel_module_request)(void);
@@ -1639,6 +1657,9 @@ struct security_operations {
                               const struct cred *cred,
                               key_perm_t perm);
        int (*key_getsecurity)(struct key *key, char **_buffer);
+       int (*key_session_to_parent)(const struct cred *cred,
+                                    const struct cred *parent_cred,
+                                    struct key *key);
 #endif /* CONFIG_KEYS */
 
 #ifdef CONFIG_AUDIT
@@ -1755,9 +1776,11 @@ int security_file_send_sigiotask(struct task_struct *tsk,
 int security_file_receive(struct file *file);
 int security_dentry_open(struct file *file, const struct cred *cred);
 int security_task_create(unsigned long clone_flags);
+int security_cred_alloc_blank(struct cred *cred, gfp_t gfp);
 void security_cred_free(struct cred *cred);
 int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp);
 void security_commit_creds(struct cred *new, const struct cred *old);
+void security_transfer_creds(struct cred *new, const struct cred *old);
 int security_kernel_act_as(struct cred *new, u32 secid);
 int security_kernel_create_files_as(struct cred *new, struct inode *inode);
 int security_kernel_module_request(void);
@@ -2286,6 +2309,9 @@ static inline int security_task_create(unsigned long clone_flags)
        return 0;
 }
 
+static inline void security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{ }
+
 static inline void security_cred_free(struct cred *cred)
 { }
 
@@ -2301,6 +2327,11 @@ static inline void security_commit_creds(struct cred *new,
 {
 }
 
+static inline void security_transfer_creds(struct cred *new,
+                                          const struct cred *old)
+{
+}
+
 static inline int security_kernel_act_as(struct cred *cred, u32 secid)
 {
        return 0;
@@ -2923,6 +2954,9 @@ void security_key_free(struct key *key);
 int security_key_permission(key_ref_t key_ref,
                            const struct cred *cred, key_perm_t perm);
 int security_key_getsecurity(struct key *key, char **_buffer);
+int security_key_session_to_parent(const struct cred *cred,
+                                  const struct cred *parent_cred,
+                                  struct key *key);
 
 #else
 
@@ -2950,6 +2984,10 @@ static inline int security_key_getsecurity(struct key *key, char **_buffer)
        return 0;
 }
 
+static inline int security_key_session_to_parent(const struct cred *cred,
+                                                const struct cred *parent_cred,
+                                                struct key *key);
+
 #endif
 #endif /* CONFIG_KEYS */
 
index 24dd2f5104b1232181f6a8e0b4099edcccb257f5..006fcab009d5053d628ef44075612f2a966f22be 100644 (file)
@@ -199,6 +199,49 @@ void exit_creds(struct task_struct *tsk)
        validate_creds(cred);
        alter_cred_subscribers(cred, -1);
        put_cred(cred);
+
+       cred = (struct cred *) tsk->replacement_session_keyring;
+       if (cred) {
+               tsk->replacement_session_keyring = NULL;
+               validate_creds(cred);
+               put_cred(cred);
+       }
+}
+
+/*
+ * Allocate blank credentials, such that the credentials can be filled in at a
+ * later date without risk of ENOMEM.
+ */
+struct cred *cred_alloc_blank(void)
+{
+       struct cred *new;
+
+       new = kmem_cache_zalloc(cred_jar, GFP_KERNEL);
+       if (!new)
+               return NULL;
+
+#ifdef CONFIG_KEYS
+       new->tgcred = kzalloc(sizeof(*new->tgcred), GFP_KERNEL);
+       if (!new->tgcred) {
+               kfree(new);
+               return NULL;
+       }
+       atomic_set(&new->tgcred->usage, 1);
+#endif
+
+       atomic_set(&new->usage, 1);
+
+       if (security_cred_alloc_blank(new, GFP_KERNEL) < 0)
+               goto error;
+
+#ifdef CONFIG_DEBUG_CREDENTIALS
+       new->magic = CRED_MAGIC;
+#endif
+       return new;
+
+error:
+       abort_creds(new);
+       return NULL;
 }
 
 /**
index 06400cf0775774b4355b35302c03fee18b930ca4..93a2ffe65905731784d5b0c6ef3f5c92a9f9ebb3 100644 (file)
@@ -373,6 +373,11 @@ static int cap_task_create(unsigned long clone_flags)
        return 0;
 }
 
+static int cap_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+       return 0;
+}
+
 static void cap_cred_free(struct cred *cred)
 {
 }
@@ -386,6 +391,10 @@ static void cap_cred_commit(struct cred *new, const struct cred *old)
 {
 }
 
+static void cap_cred_transfer(struct cred *new, const struct cred *old)
+{
+}
+
 static int cap_kernel_act_as(struct cred *new, u32 secid)
 {
        return 0;
@@ -836,6 +845,13 @@ static int cap_key_getsecurity(struct key *key, char **_buffer)
        return 0;
 }
 
+static int cap_key_session_to_parent(const struct cred *cred,
+                                    const struct cred *parent_cred,
+                                    struct key *key)
+{
+       return 0;
+}
+
 #endif /* CONFIG_KEYS */
 
 #ifdef CONFIG_AUDIT
@@ -961,9 +977,11 @@ void security_fixup_ops(struct security_operations *ops)
        set_to_cap_if_null(ops, file_receive);
        set_to_cap_if_null(ops, dentry_open);
        set_to_cap_if_null(ops, task_create);
+       set_to_cap_if_null(ops, cred_alloc_blank);
        set_to_cap_if_null(ops, cred_free);
        set_to_cap_if_null(ops, cred_prepare);
        set_to_cap_if_null(ops, cred_commit);
+       set_to_cap_if_null(ops, cred_transfer);
        set_to_cap_if_null(ops, kernel_act_as);
        set_to_cap_if_null(ops, kernel_create_files_as);
        set_to_cap_if_null(ops, kernel_module_request);
@@ -1063,6 +1081,7 @@ void security_fixup_ops(struct security_operations *ops)
        set_to_cap_if_null(ops, key_free);
        set_to_cap_if_null(ops, key_permission);
        set_to_cap_if_null(ops, key_getsecurity);
+       set_to_cap_if_null(ops, key_session_to_parent);
 #endif /* CONFIG_KEYS */
 #ifdef CONFIG_AUDIT
        set_to_cap_if_null(ops, audit_rule_init);
index c766c68a63bc2078c62bf256730e05e14b36268b..792c0a611a6d115a5a272ca748af6c1804c762ef 100644 (file)
@@ -82,6 +82,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
        case KEYCTL_GET_SECURITY:
                return keyctl_get_security(arg2, compat_ptr(arg3), arg4);
 
+       case KEYCTL_SESSION_TO_PARENT:
+               return keyctl_session_to_parent();
+
        default:
                return -EOPNOTSUPP;
        }
index 44adc325e15c467cc661c4c8308be35ef79ba0ef..1e616aef55fdaebc42615475ecce4f43e929f96c 100644 (file)
@@ -65,6 +65,7 @@ static void key_gc_timer_func(unsigned long data)
  * - return true if we altered the keyring
  */
 static bool key_gc_keyring(struct key *keyring, time_t limit)
+       __releases(key_serial_lock)
 {
        struct keyring_list *klist;
        struct key *key;
index fb830514c337984cf8608d3878e4db0b8154f477..24ba0307b7ad4a98382fd348a813d9722f85b444 100644 (file)
@@ -201,6 +201,7 @@ extern long keyctl_set_timeout(key_serial_t, unsigned);
 extern long keyctl_assume_authority(key_serial_t);
 extern long keyctl_get_security(key_serial_t keyid, char __user *buffer,
                                size_t buflen);
+extern long keyctl_session_to_parent(void);
 
 /*
  * debugging key validation
index 736d7800f97f9bd2c0f20c8599432971b0dd57d0..74c96852459281cab2b959a0b2db55666c7c9a7b 100644 (file)
@@ -1228,6 +1228,105 @@ long keyctl_get_security(key_serial_t keyid,
        return ret;
 }
 
+/*
+ * attempt to install the calling process's session keyring on the process's
+ * parent process
+ * - the keyring must exist and must grant us LINK permission
+ * - implements keyctl(KEYCTL_SESSION_TO_PARENT)
+ */
+long keyctl_session_to_parent(void)
+{
+       struct task_struct *me, *parent;
+       const struct cred *mycred, *pcred;
+       struct cred *cred, *oldcred;
+       key_ref_t keyring_r;
+       int ret;
+
+       keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
+       if (IS_ERR(keyring_r))
+               return PTR_ERR(keyring_r);
+
+       /* our parent is going to need a new cred struct, a new tgcred struct
+        * and new security data, so we allocate them here to prevent ENOMEM in
+        * our parent */
+       ret = -ENOMEM;
+       cred = cred_alloc_blank();
+       if (!cred)
+               goto error_keyring;
+
+       cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
+       keyring_r = NULL;
+
+       me = current;
+       write_lock_irq(&tasklist_lock);
+
+       parent = me->real_parent;
+       ret = -EPERM;
+
+       /* the parent mustn't be init and mustn't be a kernel thread */
+       if (parent->pid <= 1 || !parent->mm)
+               goto not_permitted;
+
+       /* the parent must be single threaded */
+       if (atomic_read(&parent->signal->count) != 1)
+               goto not_permitted;
+
+       /* the parent and the child must have different session keyrings or
+        * there's no point */
+       mycred = current_cred();
+       pcred = __task_cred(parent);
+       if (mycred == pcred ||
+           mycred->tgcred->session_keyring == pcred->tgcred->session_keyring)
+               goto already_same;
+
+       /* the parent must have the same effective ownership and mustn't be
+        * SUID/SGID */
+       if (pcred-> uid != mycred->euid ||
+           pcred->euid != mycred->euid ||
+           pcred->suid != mycred->euid ||
+           pcred-> gid != mycred->egid ||
+           pcred->egid != mycred->egid ||
+           pcred->sgid != mycred->egid)
+               goto not_permitted;
+
+       /* the keyrings must have the same UID */
+       if (pcred ->tgcred->session_keyring->uid != mycred->euid ||
+           mycred->tgcred->session_keyring->uid != mycred->euid)
+               goto not_permitted;
+
+       /* the LSM must permit the replacement of the parent's keyring with the
+        * keyring from this process */
+       ret = security_key_session_to_parent(mycred, pcred,
+                                            key_ref_to_ptr(keyring_r));
+       if (ret < 0)
+               goto not_permitted;
+
+       /* if there's an already pending keyring replacement, then we replace
+        * that */
+       oldcred = parent->replacement_session_keyring;
+
+       /* the replacement session keyring is applied just prior to userspace
+        * restarting */
+       parent->replacement_session_keyring = cred;
+       cred = NULL;
+       set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
+
+       write_unlock_irq(&tasklist_lock);
+       if (oldcred)
+               put_cred(oldcred);
+       return 0;
+
+already_same:
+       ret = 0;
+not_permitted:
+       put_cred(cred);
+       return ret;
+
+error_keyring:
+       key_ref_put(keyring_r);
+       return ret;
+}
+
 /*****************************************************************************/
 /*
  * the key control system call
@@ -1313,6 +1412,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
                                           (char __user *) arg3,
                                           (size_t) arg4);
 
+       case KEYCTL_SESSION_TO_PARENT:
+               return keyctl_session_to_parent();
+
        default:
                return -EOPNOTSUPP;
        }
index 4739cfbb41b7696b4d537eef60e8a569d5a053dd..5c23afb31ece464ad0165e1a3fb052a8969244c5 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/fs.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
+#include <linux/security.h>
 #include <linux/user_namespace.h>
 #include <asm/uaccess.h>
 #include "internal.h"
@@ -768,3 +769,51 @@ error:
        abort_creds(new);
        return ret;
 }
+
+/*
+ * Replace a process's session keyring when that process resumes userspace on
+ * behalf of one of its children
+ */
+void key_replace_session_keyring(void)
+{
+       const struct cred *old;
+       struct cred *new;
+
+       if (!current->replacement_session_keyring)
+               return;
+
+       write_lock_irq(&tasklist_lock);
+       new = current->replacement_session_keyring;
+       current->replacement_session_keyring = NULL;
+       write_unlock_irq(&tasklist_lock);
+
+       if (!new)
+               return;
+
+       old = current_cred();
+       new->  uid      = old->  uid;
+       new-> euid      = old-> euid;
+       new-> suid      = old-> suid;
+       new->fsuid      = old->fsuid;
+       new->  gid      = old->  gid;
+       new-> egid      = old-> egid;
+       new-> sgid      = old-> sgid;
+       new->fsgid      = old->fsgid;
+       new->user       = get_uid(old->user);
+       new->group_info = get_group_info(old->group_info);
+
+       new->securebits = old->securebits;
+       new->cap_inheritable    = old->cap_inheritable;
+       new->cap_permitted      = old->cap_permitted;
+       new->cap_effective      = old->cap_effective;
+       new->cap_bset           = old->cap_bset;
+
+       new->jit_keyring        = old->jit_keyring;
+       new->thread_keyring     = key_get(old->thread_keyring);
+       new->tgcred->tgid       = old->tgcred->tgid;
+       new->tgcred->process_keyring = key_get(old->tgcred->process_keyring);
+
+       security_transfer_creds(new, old);
+
+       commit_creds(new);
+}
index f88eaf6b14cce8ac8753ac5be88561000c3be11c..d8b727637f027556942b0417c96081640fc3d2f5 100644 (file)
@@ -684,6 +684,11 @@ int security_task_create(unsigned long clone_flags)
        return security_ops->task_create(clone_flags);
 }
 
+int security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+       return security_ops->cred_alloc_blank(cred, gfp);
+}
+
 void security_cred_free(struct cred *cred)
 {
        security_ops->cred_free(cred);
@@ -699,6 +704,11 @@ void security_commit_creds(struct cred *new, const struct cred *old)
        security_ops->cred_commit(new, old);
 }
 
+void security_transfer_creds(struct cred *new, const struct cred *old)
+{
+       security_ops->cred_transfer(new, old);
+}
+
 int security_kernel_act_as(struct cred *new, u32 secid)
 {
        return security_ops->kernel_act_as(new, secid);
@@ -1241,6 +1251,13 @@ int security_key_getsecurity(struct key *key, char **_buffer)
        return security_ops->key_getsecurity(key, _buffer);
 }
 
+int security_key_session_to_parent(const struct cred *cred,
+                                  const struct cred *parent_cred,
+                                  struct key *key)
+{
+       return security_ops->key_session_to_parent(cred, parent_cred, key);
+}
+
 #endif /* CONFIG_KEYS */
 
 #ifdef CONFIG_AUDIT
index c3bb31ecc5aad63c72418c2275df37db5ba544e9..134a9c0d20048914c168b8b61152a83b55ba9560 100644 (file)
@@ -3232,6 +3232,21 @@ static int selinux_task_create(unsigned long clone_flags)
        return current_has_perm(current, PROCESS__FORK);
 }
 
+/*
+ * allocate the SELinux part of blank credentials
+ */
+static int selinux_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+       struct task_security_struct *tsec;
+
+       tsec = kzalloc(sizeof(struct task_security_struct), gfp);
+       if (!tsec)
+               return -ENOMEM;
+
+       cred->security = tsec;
+       return 0;
+}
+
 /*
  * detach and free the LSM part of a set of credentials
  */
@@ -3263,6 +3278,17 @@ static int selinux_cred_prepare(struct cred *new, const struct cred *old,
        return 0;
 }
 
+/*
+ * transfer the SELinux data to a blank set of creds
+ */
+static void selinux_cred_transfer(struct cred *new, const struct cred *old)
+{
+       const struct task_security_struct *old_tsec = old->security;
+       struct task_security_struct *tsec = new->security;
+
+       *tsec = *old_tsec;
+}
+
 /*
  * set the security data for a kernel service
  * - all the creation contexts are set to unlabelled
@@ -5469,8 +5495,10 @@ static struct security_operations selinux_ops = {
        .dentry_open =                  selinux_dentry_open,
 
        .task_create =                  selinux_task_create,
+       .cred_alloc_blank =             selinux_cred_alloc_blank,
        .cred_free =                    selinux_cred_free,
        .cred_prepare =                 selinux_cred_prepare,
+       .cred_transfer =                selinux_cred_transfer,
        .kernel_act_as =                selinux_kernel_act_as,
        .kernel_create_files_as =       selinux_kernel_create_files_as,
        .kernel_module_request =        selinux_kernel_module_request,
index c243a2b25832acf9db5c226862238203d8a41c68..969f5fee19062e4030cf4de70d68b3b508c92f60 100644 (file)
@@ -1079,6 +1079,22 @@ static int smack_file_receive(struct file *file)
  * Task hooks
  */
 
+/**
+ * smack_cred_alloc_blank - "allocate" blank task-level security credentials
+ * @new: the new credentials
+ * @gfp: the atomicity of any memory allocations
+ *
+ * Prepare a blank set of credentials for modification.  This must allocate all
+ * the memory the LSM module might require such that cred_transfer() can
+ * complete without error.
+ */
+static int smack_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+       cred->security = NULL;
+       return 0;
+}
+
+
 /**
  * smack_cred_free - "free" task-level security credentials
  * @cred: the credentials in question
@@ -1116,6 +1132,18 @@ static void smack_cred_commit(struct cred *new, const struct cred *old)
 {
 }
 
+/**
+ * smack_cred_transfer - Transfer the old credentials to the new credentials
+ * @new: the new credentials
+ * @old: the original credentials
+ *
+ * Fill in a set of blank credentials from another set of credentials.
+ */
+static void smack_cred_transfer(struct cred *new, const struct cred *old)
+{
+       new->security = old->security;
+}
+
 /**
  * smack_kernel_act_as - Set the subjective context in a set of credentials
  * @new: points to the set of credentials to be modified.
@@ -3073,9 +3101,11 @@ struct security_operations smack_ops = {
        .file_send_sigiotask =          smack_file_send_sigiotask,
        .file_receive =                 smack_file_receive,
 
+       .cred_alloc_blank =             smack_cred_alloc_blank,
        .cred_free =                    smack_cred_free,
        .cred_prepare =                 smack_cred_prepare,
        .cred_commit =                  smack_cred_commit,
+       .cred_transfer =                smack_cred_transfer,
        .kernel_act_as =                smack_kernel_act_as,
        .kernel_create_files_as =       smack_kernel_create_files_as,
        .task_setpgid =                 smack_task_setpgid,
index 35a13e7915e41dba8e47253e596bb8e53c1749bd..9548a0984cc43674c8b5470ef5931bf71ac9cb1e 100644 (file)
 #include "tomoyo.h"
 #include "realpath.h"
 
+static int tomoyo_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+       new->security = NULL;
+       return 0;
+}
+
 static int tomoyo_cred_prepare(struct cred *new, const struct cred *old,
                               gfp_t gfp)
 {
@@ -25,6 +31,15 @@ static int tomoyo_cred_prepare(struct cred *new, const struct cred *old,
        return 0;
 }
 
+static void tomoyo_cred_transfer(struct cred *new, const struct cred *old)
+{
+       /*
+        * Since "struct tomoyo_domain_info *" is a sharable pointer,
+        * we don't need to duplicate.
+        */
+       new->security = old->security;
+}
+
 static int tomoyo_bprm_set_creds(struct linux_binprm *bprm)
 {
        int rc;
@@ -262,7 +277,9 @@ static int tomoyo_dentry_open(struct file *f, const struct cred *cred)
  */
 static struct security_operations tomoyo_security_ops = {
        .name                = "tomoyo",
+       .cred_alloc_blank    = tomoyo_cred_alloc_blank,
        .cred_prepare        = tomoyo_cred_prepare,
+       .cred_transfer       = tomoyo_cred_transfer,
        .bprm_set_creds      = tomoyo_bprm_set_creds,
        .bprm_check_security = tomoyo_bprm_check_security,
 #ifdef CONFIG_SYSCTL