Skip to content

Commit a6f76f2

Browse files
dhowellsJames Morris
authored andcommitted
CRED: Make execve() take advantage of copy-on-write credentials
Make execve() take advantage of copy-on-write credentials, allowing it to set up the credentials in advance, and then commit the whole lot after the point of no return. This patch and the preceding patches have been tested with the LTP SELinux testsuite. This patch makes several logical sets of alteration: (1) execve(). The credential bits from struct linux_binprm are, for the most part, replaced with a single credentials pointer (bprm->cred). This means that all the creds can be calculated in advance and then applied at the point of no return with no possibility of failure. I would like to replace bprm->cap_effective with: cap_isclear(bprm->cap_effective) but this seems impossible due to special behaviour for processes of pid 1 (they always retain their parent's capability masks where normally they'd be changed - see cap_bprm_set_creds()). The following sequence of events now happens: (a) At the start of do_execve, the current task's cred_exec_mutex is locked to prevent PTRACE_ATTACH from obsoleting the calculation of creds that we make. (a) prepare_exec_creds() is then called to make a copy of the current task's credentials and prepare it. This copy is then assigned to bprm->cred. This renders security_bprm_alloc() and security_bprm_free() unnecessary, and so they've been removed. (b) The determination of unsafe execution is now performed immediately after (a) rather than later on in the code. The result is stored in bprm->unsafe for future reference. (c) prepare_binprm() is called, possibly multiple times. (i) This applies the result of set[ug]id binaries to the new creds attached to bprm->cred. Personality bit clearance is recorded, but now deferred on the basis that the exec procedure may yet fail. (ii) This then calls the new security_bprm_set_creds(). This should calculate the new LSM and capability credentials into *bprm->cred. This folds together security_bprm_set() and parts of security_bprm_apply_creds() (these two have been removed). Anything that might fail must be done at this point. (iii) bprm->cred_prepared is set to 1. bprm->cred_prepared is 0 on the first pass of the security calculations, and 1 on all subsequent passes. This allows SELinux in (ii) to base its calculations only on the initial script and not on the interpreter. (d) flush_old_exec() is called to commit the task to execution. This performs the following steps with regard to credentials: (i) Clear pdeath_signal and set dumpable on certain circumstances that may not be covered by commit_creds(). (ii) Clear any bits in current->personality that were deferred from (c.i). (e) install_exec_creds() [compute_creds() as was] is called to install the new credentials. This performs the following steps with regard to credentials: (i) Calls security_bprm_committing_creds() to apply any security requirements, such as flushing unauthorised files in SELinux, that must be done before the credentials are changed. This is made up of bits of security_bprm_apply_creds() and security_bprm_post_apply_creds(), both of which have been removed. This function is not allowed to fail; anything that might fail must have been done in (c.ii). (ii) Calls commit_creds() to apply the new credentials in a single assignment (more or less). Possibly pdeath_signal and dumpable should be part of struct creds. (iii) Unlocks the task's cred_replace_mutex, thus allowing PTRACE_ATTACH to take place. (iv) Clears The bprm->cred pointer as the credentials it was holding are now immutable. (v) Calls security_bprm_committed_creds() to apply any security alterations that must be done after the creds have been changed. SELinux uses this to flush signals and signal handlers. (f) If an error occurs before (d.i), bprm_free() will call abort_creds() to destroy the proposed new credentials and will then unlock cred_replace_mutex. No changes to the credentials will have been made. (2) LSM interface. A number of functions have been changed, added or removed: (*) security_bprm_alloc(), ->bprm_alloc_security() (*) security_bprm_free(), ->bprm_free_security() Removed in favour of preparing new credentials and modifying those. (*) security_bprm_apply_creds(), ->bprm_apply_creds() (*) security_bprm_post_apply_creds(), ->bprm_post_apply_creds() Removed; split between security_bprm_set_creds(), security_bprm_committing_creds() and security_bprm_committed_creds(). (*) security_bprm_set(), ->bprm_set_security() Removed; folded into security_bprm_set_creds(). (*) security_bprm_set_creds(), ->bprm_set_creds() New. The new credentials in bprm->creds should be checked and set up as appropriate. bprm->cred_prepared is 0 on the first call, 1 on the second and subsequent calls. (*) security_bprm_committing_creds(), ->bprm_committing_creds() (*) security_bprm_committed_creds(), ->bprm_committed_creds() New. Apply the security effects of the new credentials. This includes closing unauthorised files in SELinux. This function may not fail. When the former is called, the creds haven't yet been applied to the process; when the latter is called, they have. The former may access bprm->cred, the latter may not. (3) SELinux. SELinux has a number of changes, in addition to those to support the LSM interface changes mentioned above: (a) The bprm_security_struct struct has been removed in favour of using the credentials-under-construction approach. (c) flush_unauthorized_files() now takes a cred pointer and passes it on to inode_has_perm(), file_has_perm() and dentry_open(). Signed-off-by: David Howells <dhowells@redhat.com> Acked-by: James Morris <jmorris@namei.org> Acked-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: James Morris <jmorris@namei.org>
1 parent d84f4f9 commit a6f76f2

File tree

23 files changed

+429
-515
lines changed

23 files changed

+429
-515
lines changed

arch/x86/ia32/ia32_aout.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ static int load_aout_binary(struct linux_binprm *bprm, struct pt_regs *regs)
327327
current->mm->cached_hole_size = 0;
328328

329329
current->mm->mmap = NULL;
330-
compute_creds(bprm);
330+
install_exec_creds(bprm);
331331
current->flags &= ~PF_FORKNOEXEC;
332332

333333
if (N_MAGIC(ex) == OMAGIC) {

fs/binfmt_aout.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
320320
current->mm->free_area_cache = current->mm->mmap_base;
321321
current->mm->cached_hole_size = 0;
322322

323-
compute_creds(bprm);
323+
install_exec_creds(bprm);
324324
current->flags &= ~PF_FORKNOEXEC;
325325
#ifdef __sparc__
326326
if (N_MAGIC(ex) == NMAGIC) {

fs/binfmt_elf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
956956
}
957957
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
958958

959-
compute_creds(bprm);
959+
install_exec_creds(bprm);
960960
current->flags &= ~PF_FORKNOEXEC;
961961
retval = create_elf_tables(bprm, &loc->elf_ex,
962962
load_addr, interp_load_addr);

fs/binfmt_elf_fdpic.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm,
404404
current->mm->start_stack = current->mm->start_brk + stack_size;
405405
#endif
406406

407-
compute_creds(bprm);
407+
install_exec_creds(bprm);
408408
current->flags &= ~PF_FORKNOEXEC;
409409
if (create_elf_fdpic_tables(bprm, current->mm,
410410
&exec_params, &interp_params) < 0)

fs/binfmt_flat.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ static int load_flat_binary(struct linux_binprm * bprm, struct pt_regs * regs)
880880
(libinfo.lib_list[j].loaded)?
881881
libinfo.lib_list[j].start_data:UNLOADED_LIB;
882882

883-
compute_creds(bprm);
883+
install_exec_creds(bprm);
884884
current->flags &= ~PF_FORKNOEXEC;
885885

886886
set_binfmt(&flat_format);

fs/binfmt_som.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ load_som_binary(struct linux_binprm * bprm, struct pt_regs * regs)
255255
kfree(hpuxhdr);
256256

257257
set_binfmt(&som_format);
258-
compute_creds(bprm);
258+
install_exec_creds(bprm);
259259
setup_arg_pages(bprm, STACK_TOP, EXSTACK_DEFAULT);
260260

261261
create_som_tables(bprm);

fs/compat.c

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,10 +1393,20 @@ int compat_do_execve(char * filename,
13931393
if (!bprm)
13941394
goto out_ret;
13951395

1396+
retval = mutex_lock_interruptible(&current->cred_exec_mutex);
1397+
if (retval < 0)
1398+
goto out_free;
1399+
1400+
retval = -ENOMEM;
1401+
bprm->cred = prepare_exec_creds();
1402+
if (!bprm->cred)
1403+
goto out_unlock;
1404+
check_unsafe_exec(bprm);
1405+
13961406
file = open_exec(filename);
13971407
retval = PTR_ERR(file);
13981408
if (IS_ERR(file))
1399-
goto out_kfree;
1409+
goto out_unlock;
14001410

14011411
sched_exec();
14021412

@@ -1410,14 +1420,10 @@ int compat_do_execve(char * filename,
14101420

14111421
bprm->argc = compat_count(argv, MAX_ARG_STRINGS);
14121422
if ((retval = bprm->argc) < 0)
1413-
goto out_mm;
1423+
goto out;
14141424

14151425
bprm->envc = compat_count(envp, MAX_ARG_STRINGS);
14161426
if ((retval = bprm->envc) < 0)
1417-
goto out_mm;
1418-
1419-
retval = security_bprm_alloc(bprm);
1420-
if (retval)
14211427
goto out;
14221428

14231429
retval = prepare_binprm(bprm);
@@ -1438,19 +1444,16 @@ int compat_do_execve(char * filename,
14381444
goto out;
14391445

14401446
retval = search_binary_handler(bprm, regs);
1441-
if (retval >= 0) {
1442-
/* execve success */
1443-
security_bprm_free(bprm);
1444-
acct_update_integrals(current);
1445-
free_bprm(bprm);
1446-
return retval;
1447-
}
1447+
if (retval < 0)
1448+
goto out;
14481449

1449-
out:
1450-
if (bprm->security)
1451-
security_bprm_free(bprm);
1450+
/* execve succeeded */
1451+
mutex_unlock(&current->cred_exec_mutex);
1452+
acct_update_integrals(current);
1453+
free_bprm(bprm);
1454+
return retval;
14521455

1453-
out_mm:
1456+
out:
14541457
if (bprm->mm)
14551458
mmput(bprm->mm);
14561459

@@ -1460,7 +1463,10 @@ int compat_do_execve(char * filename,
14601463
fput(bprm->file);
14611464
}
14621465

1463-
out_kfree:
1466+
out_unlock:
1467+
mutex_unlock(&current->cred_exec_mutex);
1468+
1469+
out_free:
14641470
free_bprm(bprm);
14651471

14661472
out_ret:

fs/exec.c

Lines changed: 86 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <asm/uaccess.h>
5656
#include <asm/mmu_context.h>
5757
#include <asm/tlb.h>
58+
#include "internal.h"
5859

5960
#ifdef __alpha__
6061
/* for /sbin/loader handling in search_binary_handler() */
@@ -1007,15 +1008,17 @@ int flush_old_exec(struct linux_binprm * bprm)
10071008
*/
10081009
current->mm->task_size = TASK_SIZE;
10091010

1010-
if (bprm->e_uid != current_euid() ||
1011-
bprm->e_gid != current_egid()) {
1012-
set_dumpable(current->mm, suid_dumpable);
1011+
/* install the new credentials */
1012+
if (bprm->cred->uid != current_euid() ||
1013+
bprm->cred->gid != current_egid()) {
10131014
current->pdeath_signal = 0;
10141015
} else if (file_permission(bprm->file, MAY_READ) ||
1015-
(bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)) {
1016+
bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP) {
10161017
set_dumpable(current->mm, suid_dumpable);
10171018
}
10181019

1020+
current->personality &= ~bprm->per_clear;
1021+
10191022
/* An exec changes our domain. We are no longer part of the thread
10201023
group */
10211024

@@ -1032,28 +1035,66 @@ int flush_old_exec(struct linux_binprm * bprm)
10321035

10331036
EXPORT_SYMBOL(flush_old_exec);
10341037

1038+
/*
1039+
* install the new credentials for this executable
1040+
*/
1041+
void install_exec_creds(struct linux_binprm *bprm)
1042+
{
1043+
security_bprm_committing_creds(bprm);
1044+
1045+
commit_creds(bprm->cred);
1046+
bprm->cred = NULL;
1047+
1048+
/* cred_exec_mutex must be held at least to this point to prevent
1049+
* ptrace_attach() from altering our determination of the task's
1050+
* credentials; any time after this it may be unlocked */
1051+
1052+
security_bprm_committed_creds(bprm);
1053+
}
1054+
EXPORT_SYMBOL(install_exec_creds);
1055+
1056+
/*
1057+
* determine how safe it is to execute the proposed program
1058+
* - the caller must hold current->cred_exec_mutex to protect against
1059+
* PTRACE_ATTACH
1060+
*/
1061+
void check_unsafe_exec(struct linux_binprm *bprm)
1062+
{
1063+
struct task_struct *p = current;
1064+
1065+
bprm->unsafe = tracehook_unsafe_exec(p);
1066+
1067+
if (atomic_read(&p->fs->count) > 1 ||
1068+
atomic_read(&p->files->count) > 1 ||
1069+
atomic_read(&p->sighand->count) > 1)
1070+
bprm->unsafe |= LSM_UNSAFE_SHARE;
1071+
}
1072+
10351073
/*
10361074
* Fill the binprm structure from the inode.
10371075
* Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
1076+
*
1077+
* This may be called multiple times for binary chains (scripts for example).
10381078
*/
10391079
int prepare_binprm(struct linux_binprm *bprm)
10401080
{
1041-
int mode;
1081+
umode_t mode;
10421082
struct inode * inode = bprm->file->f_path.dentry->d_inode;
10431083
int retval;
10441084

10451085
mode = inode->i_mode;
10461086
if (bprm->file->f_op == NULL)
10471087
return -EACCES;
10481088

1049-
bprm->e_uid = current_euid();
1050-
bprm->e_gid = current_egid();
1089+
/* clear any previous set[ug]id data from a previous binary */
1090+
bprm->cred->euid = current_euid();
1091+
bprm->cred->egid = current_egid();
10511092

1052-
if(!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
1093+
if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
10531094
/* Set-uid? */
10541095
if (mode & S_ISUID) {
1055-
current->personality &= ~PER_CLEAR_ON_SETID;
1056-
bprm->e_uid = inode->i_uid;
1096+
bprm->per_clear |= PER_CLEAR_ON_SETID;
1097+
bprm->cred->euid = inode->i_uid;
10571098
}
10581099

10591100
/* Set-gid? */
@@ -1063,50 +1104,23 @@ int prepare_binprm(struct linux_binprm *bprm)
10631104
* executable.
10641105
*/
10651106
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
1066-
current->personality &= ~PER_CLEAR_ON_SETID;
1067-
bprm->e_gid = inode->i_gid;
1107+
bprm->per_clear |= PER_CLEAR_ON_SETID;
1108+
bprm->cred->egid = inode->i_gid;
10681109
}
10691110
}
10701111

10711112
/* fill in binprm security blob */
1072-
retval = security_bprm_set(bprm);
1113+
retval = security_bprm_set_creds(bprm);
10731114
if (retval)
10741115
return retval;
1116+
bprm->cred_prepared = 1;
10751117

1076-
memset(bprm->buf,0,BINPRM_BUF_SIZE);
1077-
return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
1118+
memset(bprm->buf, 0, BINPRM_BUF_SIZE);
1119+
return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
10781120
}
10791121

10801122
EXPORT_SYMBOL(prepare_binprm);
10811123

1082-
static int unsafe_exec(struct task_struct *p)
1083-
{
1084-
int unsafe = tracehook_unsafe_exec(p);
1085-
1086-
if (atomic_read(&p->fs->count) > 1 ||
1087-
atomic_read(&p->files->count) > 1 ||
1088-
atomic_read(&p->sighand->count) > 1)
1089-
unsafe |= LSM_UNSAFE_SHARE;
1090-
1091-
return unsafe;
1092-
}
1093-
1094-
void compute_creds(struct linux_binprm *bprm)
1095-
{
1096-
int unsafe;
1097-
1098-
if (bprm->e_uid != current_uid())
1099-
current->pdeath_signal = 0;
1100-
exec_keys(current);
1101-
1102-
task_lock(current);
1103-
unsafe = unsafe_exec(current);
1104-
security_bprm_apply_creds(bprm, unsafe);
1105-
task_unlock(current);
1106-
security_bprm_post_apply_creds(bprm);
1107-
}
1108-
EXPORT_SYMBOL(compute_creds);
1109-
11101124
/*
11111125
* Arguments are '\0' separated strings found at the location bprm->p
11121126
* points to; chop off the first by relocating brpm->p to right after
@@ -1259,6 +1273,8 @@ EXPORT_SYMBOL(search_binary_handler);
12591273
void free_bprm(struct linux_binprm *bprm)
12601274
{
12611275
free_arg_pages(bprm);
1276+
if (bprm->cred)
1277+
abort_creds(bprm->cred);
12621278
kfree(bprm);
12631279
}
12641280

@@ -1284,10 +1300,20 @@ int do_execve(char * filename,
12841300
if (!bprm)
12851301
goto out_files;
12861302

1303+
retval = mutex_lock_interruptible(&current->cred_exec_mutex);
1304+
if (retval < 0)
1305+
goto out_free;
1306+
1307+
retval = -ENOMEM;
1308+
bprm->cred = prepare_exec_creds();
1309+
if (!bprm->cred)
1310+
goto out_unlock;
1311+
check_unsafe_exec(bprm);
1312+
12871313
file = open_exec(filename);
12881314
retval = PTR_ERR(file);
12891315
if (IS_ERR(file))
1290-
goto out_kfree;
1316+
goto out_unlock;
12911317

12921318
sched_exec();
12931319

@@ -1301,14 +1327,10 @@ int do_execve(char * filename,
13011327

13021328
bprm->argc = count(argv, MAX_ARG_STRINGS);
13031329
if ((retval = bprm->argc) < 0)
1304-
goto out_mm;
1330+
goto out;
13051331

13061332
bprm->envc = count(envp, MAX_ARG_STRINGS);
13071333
if ((retval = bprm->envc) < 0)
1308-
goto out_mm;
1309-
1310-
retval = security_bprm_alloc(bprm);
1311-
if (retval)
13121334
goto out;
13131335

13141336
retval = prepare_binprm(bprm);
@@ -1330,21 +1352,18 @@ int do_execve(char * filename,
13301352

13311353
current->flags &= ~PF_KTHREAD;
13321354
retval = search_binary_handler(bprm,regs);
1333-
if (retval >= 0) {
1334-
/* execve success */
1335-
security_bprm_free(bprm);
1336-
acct_update_integrals(current);
1337-
free_bprm(bprm);
1338-
if (displaced)
1339-
put_files_struct(displaced);
1340-
return retval;
1341-
}
1355+
if (retval < 0)
1356+
goto out;
13421357

1343-
out:
1344-
if (bprm->security)
1345-
security_bprm_free(bprm);
1358+
/* execve succeeded */
1359+
mutex_unlock(&current->cred_exec_mutex);
1360+
acct_update_integrals(current);
1361+
free_bprm(bprm);
1362+
if (displaced)
1363+
put_files_struct(displaced);
1364+
return retval;
13461365

1347-
out_mm:
1366+
out:
13481367
if (bprm->mm)
13491368
mmput (bprm->mm);
13501369

@@ -1353,7 +1372,11 @@ int do_execve(char * filename,
13531372
allow_write_access(bprm->file);
13541373
fput(bprm->file);
13551374
}
1356-
out_kfree:
1375+
1376+
out_unlock:
1377+
mutex_unlock(&current->cred_exec_mutex);
1378+
1379+
out_free:
13571380
free_bprm(bprm);
13581381

13591382
out_files:

0 commit comments

Comments
 (0)