diff options
34 files changed, 409 insertions, 0 deletions
diff --git a/Documentation/keys.txt b/Documentation/keys.txt index 203487e9b1d..e4dbbdb1bd9 100644 --- a/Documentation/keys.txt +++ b/Documentation/keys.txt @@ -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  =============== diff --git a/arch/alpha/kernel/signal.c b/arch/alpha/kernel/signal.c index 04e17c1f0f1..d91aaa74705 100644 --- a/arch/alpha/kernel/signal.c +++ b/arch/alpha/kernel/signal.c @@ -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();  	}  } diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c index 13dec276927..ea4ad3a43c8 100644 --- a/arch/arm/kernel/signal.c +++ b/arch/arm/kernel/signal.c @@ -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();  	}  } diff --git a/arch/avr32/kernel/signal.c b/arch/avr32/kernel/signal.c index 62d242e2d03..de9f7fe2aef 100644 --- a/arch/avr32/kernel/signal.c +++ b/arch/avr32/kernel/signal.c @@ -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();  	}  } diff --git a/arch/cris/kernel/ptrace.c b/arch/cris/kernel/ptrace.c index 4f06d7fb43d..32e9d5ee895 100644 --- a/arch/cris/kernel/ptrace.c +++ b/arch/cris/kernel/ptrace.c @@ -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();  	}  } diff --git a/arch/frv/kernel/signal.c b/arch/frv/kernel/signal.c index 4a7a62c6e78..6b0a2b6fed6 100644 --- a/arch/frv/kernel/signal.c +++ b/arch/frv/kernel/signal.c @@ -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() */ diff --git a/arch/h8300/kernel/signal.c b/arch/h8300/kernel/signal.c index 56b3ab7dbbb..abac3ee8c52 100644 --- a/arch/h8300/kernel/signal.c +++ b/arch/h8300/kernel/signal.c @@ -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();  	}  } diff --git a/arch/ia64/kernel/process.c b/arch/ia64/kernel/process.c index 5d7c0e5b9e7..89969e95004 100644 --- a/arch/ia64/kernel/process.c +++ b/arch/ia64/kernel/process.c @@ -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 */ diff --git a/arch/m32r/kernel/signal.c b/arch/m32r/kernel/signal.c index 3220258be18..f80bac17c65 100644 --- a/arch/m32r/kernel/signal.c +++ b/arch/m32r/kernel/signal.c @@ -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); diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c index a3d1015471d..c2acf31874a 100644 --- a/arch/mips/kernel/signal.c +++ b/arch/mips/kernel/signal.c @@ -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();  	}  } diff --git a/arch/mn10300/kernel/signal.c b/arch/mn10300/kernel/signal.c index feb2f2e810d..a21f43bc68e 100644 --- a/arch/mn10300/kernel/signal.c +++ b/arch/mn10300/kernel/signal.c @@ -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();  	}  } diff --git a/arch/parisc/kernel/signal.c b/arch/parisc/kernel/signal.c index b3bfc432670..5ca1c02b805 100644 --- a/arch/parisc/kernel/signal.c +++ b/arch/parisc/kernel/signal.c @@ -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();  	}  } diff --git a/arch/s390/kernel/signal.c b/arch/s390/kernel/signal.c index 062bd64e65f..6b4fef877f9 100644 --- a/arch/s390/kernel/signal.c +++ b/arch/s390/kernel/signal.c @@ -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();  } diff --git a/arch/sh/kernel/signal_32.c b/arch/sh/kernel/signal_32.c index b5afbec1db5..04a21883f32 100644 --- a/arch/sh/kernel/signal_32.c +++ b/arch/sh/kernel/signal_32.c @@ -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();  	}  } diff --git a/arch/sh/kernel/signal_64.c b/arch/sh/kernel/signal_64.c index 0663a0ee602..9e5c9b1d7e9 100644 --- a/arch/sh/kernel/signal_64.c +++ b/arch/sh/kernel/signal_64.c @@ -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();  	}  } diff --git a/arch/sparc/kernel/signal_32.c b/arch/sparc/kernel/signal_32.c index 181d069a2d4..7ce1a1005b1 100644 --- a/arch/sparc/kernel/signal_32.c +++ b/arch/sparc/kernel/signal_32.c @@ -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();  	}  } diff --git a/arch/sparc/kernel/signal_64.c b/arch/sparc/kernel/signal_64.c index ec82d76dc6f..647afbda7ae 100644 --- a/arch/sparc/kernel/signal_64.c +++ b/arch/sparc/kernel/signal_64.c @@ -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();  	}  } + diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index 4c578751e94..81e58238c4c 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c @@ -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 diff --git a/include/linux/cred.h b/include/linux/cred.h index 85439abdbc8..24520a539c6 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -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); diff --git a/include/linux/key.h b/include/linux/key.h index 33e0165de10..cd50dfa1d4c 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -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__ */ diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h index c0688eb7209..bd383f1944f 100644 --- a/include/linux/keyctl.h +++ b/include/linux/keyctl.h @@ -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 */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 5c7ce13c169..9304027673b 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -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 diff --git a/include/linux/security.h b/include/linux/security.h index 40ba39ea68c..97de3fe3dd0 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -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 */ diff --git a/kernel/cred.c b/kernel/cred.c index 24dd2f5104b..006fcab009d 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -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;  }  /** diff --git a/security/capability.c b/security/capability.c index 06400cf0775..93a2ffe6590 100644 --- a/security/capability.c +++ b/security/capability.c @@ -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); diff --git a/security/keys/compat.c b/security/keys/compat.c index c766c68a63b..792c0a611a6 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -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;  	} diff --git a/security/keys/gc.c b/security/keys/gc.c index 44adc325e15..1e616aef55f 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -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; diff --git a/security/keys/internal.h b/security/keys/internal.h index fb830514c33..24ba0307b7a 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -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 diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 736d7800f97..74c96852459 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -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;  	} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 4739cfbb41b..5c23afb31ec 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -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); +} diff --git a/security/security.c b/security/security.c index f88eaf6b14c..d8b727637f0 100644 --- a/security/security.c +++ b/security/security.c @@ -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 diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index c3bb31ecc5a..134a9c0d200 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3233,6 +3233,21 @@ static int selinux_task_create(unsigned long clone_flags)  }  /* + * 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   */  static void selinux_cred_free(struct cred *cred) @@ -3264,6 +3279,17 @@ static int selinux_cred_prepare(struct cred *new, const struct cred *old,  }  /* + * 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, diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index c243a2b2583..969f5fee190 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1080,6 +1080,22 @@ static int smack_file_receive(struct file *file)   */  /** + * 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   * @@ -1117,6 +1133,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.   * @secid: specifies the security ID to be set @@ -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, diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 35a13e7915e..9548a0984cc 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -14,6 +14,12 @@  #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  |