capabilities patch update

Y2K (y2k@y2ker.com)
Wed, 28 Apr 1999 22:44:43 -0700 (PDT)


New stuff.
1)Cleaned up a few things-- some of them really dumb stuff on my part.
2)Got fR(required) updated so it at least looks right to me.
3)got FD aka fM stuff put in; David should tell me if thats what he
expected. It masks out inheritence stuff, *D*isinherite
4)Changed a few things in anticipation of future intergration with
vfs cap code that Andrew pointed out to me. CAP_SETFCAP and updated
example.
5)tore out per-task securebits stuff 'cause task_struct in sched.h seemed
overly touchy.

Booted it up in single mode and tried a few su's to make sure that things
were at least vaguely compatible and sane. They were, but I plan on making
a regression test setup before I'd recommend it too much.
Hope to get in working with Jeremy's addnote. Hope to look into some of
the libcap and other things availible.

Old Stuff.
1)Should use elf PT_NOTE "CAPS" to restrict capabilitities.
2)Should be 100% compatible with old code when using default securebits
settings and no new funny stuff. Should be highly legacy compatible if you
start playing around with capabilities through elf headers or newly
defined api. vfs cap support still needs merging in.
3)Based strongly on pre-existing kernel facilities.
4)Changes things enough so that you can actually have a chance to play
with capabilities past a call to execve..

Please do tell me what you think.
And here comes the patch against 2.2.6 ...

diff -urP linux/Documentation/Configure.help linux-cap/Documentation/Configure.help
--- linux/Documentation/Configure.help Wed Apr 28 17:40:02 1999
+++ linux-cap/Documentation/Configure.help Wed Apr 28 18:15:28 1999
@@ -1572,6 +1572,39 @@
you have use for it; the module is called binfmt_misc.o. If you
don't know what to answer at this point, say Y.

+Securebits no root !
+CONFIG_SECUREBITS_NOROOT
+ This is experimental tweak that will effect SECUREBITS_DEFAULT
+ 0 is insecure but not fixed
+ 1 is secure but not fixed
+ 2 is insecure and fixed
+ 3 is secure and fixed
+ Your userland probably doesn't understand capabilities well enough yet!
+ A secure setting will most likely cause your whole system to become unusable!
+ If you don't know what to answer at this point, say 2!
+
+Securebits no setuid fixup !
+CONFIG_SECUREBITS_NO_SETUID_FIXUP
+ This is experimental tweak that will effect SECUREBITS_DEFAULT
+ 0 is insecure but not fixed
+ 1 is secure but not fixed
+ 2 is insecure and fixed
+ 3 is secure and fixed
+ Your userland probably doesn't understand capabilities well enough yet!
+ A secure setting will most likely cause your whole system to become unusable!
+ If you don't know what to answer at this point, say 2!
+
+Securebits pure capabilites !
+CONFIG_SECUREBITS_PURE_CAP
+ This is experimental tweak that will effect SECUREBITS_DEFAULT
+ 0 is insecure but not fixed
+ 1 is secure but not fixed
+ 2 is insecure and fixed
+ 3 is secure and fixed
+ Your userland probably doesn't understand capabilities well enough yet!
+ A secure setting will most likely cause your whole system to become unusable!
+ If you don't know what to answer at this point, say 2!
+
Solaris binary emulation
CONFIG_SOLARIS_EMUL
This is experimental code which will enable you to run (many)
diff -urP linux/arch/i386/config.in linux-cap/arch/i386/config.in
--- linux/arch/i386/config.in Mon Feb 1 12:03:20 1999
+++ linux-cap/arch/i386/config.in Wed Apr 28 18:15:29 1999
@@ -112,6 +112,12 @@
bool ' Allow interrupts during APM BIOS calls' CONFIG_APM_ALLOW_INTS
fi

+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ int 'Securebits NOROOT 0,1,2,3' CONFIG_SECUREBITS_NOROOT 2
+ int 'Securebits NO_SETUID_FIXUP 0,1,2,3' CONFIG_SECUREBITS_NO_SETUID_FIXUP 2
+ int 'Securebits PURE_CAP 0,1,2,3' CONFIG_SECUREBITS_PURE_CAP 2
+fi
+
endmenu

source drivers/pnp/Config.in
diff -urP linux/cap.ldo linux-cap/cap.ldo
--- linux/cap.ldo Wed Dec 31 16:00:00 1969
+++ linux-cap/cap.ldo Wed Apr 28 18:15:29 1999
@@ -0,0 +1,26 @@
+/* sample fake cap ldo thingy */
+/* ld -i -T cap.lds cap.ldo */
+
+SECTIONS {
+ .caps (NOLOAD) : {
+LONG(4); /* name size */
+LONG(64); /* content size */
+LONG(7); /* content type */
+BYTE(67); /* C */
+BYTE(65); /* A */
+BYTE(80); /* P */
+BYTE(83); /* S */
+/* CAPS is name of note */
+LONG(0xca5ab1e); /* yet another signature */
+LONG(0); /* version */
+LONG(0); /* flags */
+LONG(1); /* xuid */
+LONG(-1); /* effective fE */
+LONG(0); /* permitted fP */
+LONG(-1); /* inheritable fI */
+LONG(0); /* required fR */
+LONG(0); /* known fK -- another source of version info? */
+LONG(0); /* disinherite fD aka fM */
+LONG(0); /* padding */
+ } :pcaps
+}
diff -urP linux/cap.lds linux-cap/cap.lds
--- linux/cap.lds Wed Dec 31 16:00:00 1969
+++ linux-cap/cap.lds Wed Apr 28 18:15:29 1999
@@ -0,0 +1,10 @@
+/* cap.lds cap ld script */
+/* ld -i -T cap.lds foo.ldo */
+
+OUTPUT(cap.o)
+PHDRS {
+pcaps PT_NOTE /* PHDRS */ ;
+}
+SECTIONS {
+ .caps (NOLOAD) : { *(.caps) } : pcaps
+}
diff -urP linux/fs/attr.c linux-cap/fs/attr.c
--- linux/fs/attr.c Fri Nov 13 10:07:26 1998
+++ linux-cap/fs/attr.c Wed Apr 28 20:32:44 1999
@@ -37,6 +37,9 @@
if (ia_valid & ATTR_MODE) {
if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER))
goto error;
+ /* Also check the setuid bit! */
+ if ( (inode->i_uid==0) && !capable(CAP_SETFCAP) )
+ attr->ia_mode &= ~S_ISUID;
/* Also check the setgid bit! */
if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
inode->i_gid) && !capable(CAP_FSETID))
@@ -74,6 +77,8 @@
inode->i_mode = attr->ia_mode;
if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID))
inode->i_mode &= ~S_ISGID;
+ if ( (inode->i_uid==0) && !capable(CAP_SETFCAP) )
+ attr->ia_mode &= ~S_ISUID;
}
mark_inode_dirty(inode);
}
diff -urP linux/fs/binfmt_elf.c linux-cap/fs/binfmt_elf.c
--- linux/fs/binfmt_elf.c Wed Apr 28 17:39:29 1999
+++ linux-cap/fs/binfmt_elf.c Wed Apr 28 18:15:29 1999
@@ -396,6 +396,61 @@
#define INTERPRETER_AOUT 1
#define INTERPRETER_ELF 2

+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+
+/* Given a pointer to a set of notes, find the first with the given
+ name and type */
+static void *find_note(char *buf, int bufsz, const char *notename, int type)
+{
+ while(bufsz > sizeof(Elf32_Nhdr)) {
+ Elf32_Nhdr *nhdr = (Elf32_Nhdr *)buf;
+ char *name;
+ void *desc;
+ int sz;
+
+ sz = sizeof(Elf32_Nhdr) +
+ roundup(nhdr->n_namesz, 4) +
+ roundup(nhdr->n_descsz, 4);
+ if (bufsz < sz)
+ break;
+ name = buf + sizeof(Elf32_Nhdr);
+ desc = name + roundup(nhdr->n_namesz, 4);
+
+ if (strncmp(name, notename, nhdr->n_namesz) == 0 &&
+ type == nhdr->n_type)
+ return desc;
+
+ buf += sz;
+ bufsz -= sz;
+ }
+
+ return NULL;
+}
+
+static inline int
+do_elfcap_restrict(struct linux_binprm *bprm,struct elf_capabilities_note *note)
+{
+ /* check version */
+ if (note->cap.signature!=0xca5ab1e || note->cap.version)
+ return 0;
+ /* You may want to loose owner's uid */
+ if (note->cap.flags & ECF_MAKE_EUID_UID)
+ bprm->e_uid = current->uid;
+ /* We only honour random uid changes for root */
+ /* we also care about CAP_SETUID here */
+ if (!bprm->e_uid&&(note->cap.flags&ECF_MAKE_EUID_XUID)&&
+ (capable(CAP_SETUID)||cap_raised(bprm->cap_permitted,CAP_SETUID)))
+ bprm->e_uid = note->cap.xuid;
+
+ cap_mask( bprm->cap_effective, note->cap.effective );
+ cap_mask( bprm->cap_permitted, note->cap.permitted );
+ cap_mask( bprm->cap_inheritable, note->cap.inheritable );
+ bprm->cap_disinherite=note->cap.disinherite;
+
+ if (!cap_issubset(note->cap.required,bprm->cap_inheritable) )
+ return -EPERM;
+ return 0;
+}

static inline int
do_load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
@@ -425,8 +480,10 @@

retval = -ENOEXEC;
/* First of all, some simple consistency checks */
- if (elf_ex.e_ident[0] != 0x7f ||
- strncmp(&elf_ex.e_ident[1], "ELF", 3) != 0)
+ if (elf_ex.e_ident[0] != 0x7f )
+ goto out;
+ if (strncmp(&elf_ex.e_ident[1], "ELF", 3) &&
+ strncmp(&elf_ex.e_ident[1], "FLE", 3))
goto out;

if (elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN)
@@ -473,7 +530,48 @@
end_data = 0;

for (i = 0; i < elf_ex.e_phnum; i++) {
- if (elf_ppnt->p_type == PT_INTERP) {
+ switch(elf_ppnt->p_type) {
+ case PT_NOTE: {
+ struct elf_capabilities_note *caps;
+ char *buf;
+ int subret = 0;
+
+ retval = -ENOMEM;
+ buf = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
+
+ if (buf == NULL)
+ goto out_free_dentry;
+
+ retval = read_exec(bprm->dentry, elf_ppnt->p_offset,
+ buf, elf_ppnt->p_filesz, 1);
+
+ if (retval < 0) {
+ kfree(buf);
+ goto out_free_dentry;
+ }
+
+ caps = (struct elf_capabilities_note *)find_note(buf,
+ elf_ppnt->p_filesz, "CAPS", NT_ELF_CAP);
+
+ if (caps != NULL) {
+ /* what to do with multiple CAPS notes?
+ Find the most restrictive union? */
+
+ /* process caps here */
+ subret=do_elfcap_restrict(bprm, caps);
+
+ printk("found CAPS note!\n");
+ }
+
+ kfree(buf);
+ if (subret) {
+ retval=subret;
+ goto out_free_ph;
+ }
+ }
+ break;
+
+ case PT_INTERP: {
retval = -EINVAL;
if (elf_interpreter)
goto out_free_interp;
@@ -533,6 +631,8 @@
interp_ex = *((struct exec *) bprm->buf);
interp_elf_ex = *((struct elfhdr *) bprm->buf);
}
+ break;
+ }
elf_ppnt++;
}

@@ -777,7 +877,8 @@

/* error cleanup */
out_free_dentry:
- dput(interpreter_dentry);
+ if (interpreter_dentry)
+ dput(interpreter_dentry);
out_free_interp:
if (elf_interpreter)
kfree(elf_interpreter);
diff -urP linux/fs/exec.c linux-cap/fs/exec.c
--- linux/fs/exec.c Mon Jan 18 13:47:38 1999
+++ linux-cap/fs/exec.c Wed Apr 28 19:14:26 1999
@@ -601,29 +601,56 @@
id_change = 1;
}

- /* We don't have VFS support for capabilities yet */
- cap_clear(bprm->cap_inheritable);
- cap_clear(bprm->cap_permitted);
- cap_clear(bprm->cap_effective);
+ cap_clear(bprm->cap_disinherite);
+ /* This patch doesn't have VFS support for capabilities yet */
+ /* It might look something like this when it does */
+ /*
+ * int subret;
+ * subret=vfs_cap_dentry(dentry,CAP_FS_GET,
+ * &bprm->cap_effective,
+ * &bprm->cap_inheritable,
+ * &bprm->cap_permitted);
+ * if (subret==-ENOSUP)
+ * USE_DEFAULTS_AS_BELOW
+ * else if (subret)
+ * return subret;
+ * else cap_mask(bprm->cap_inheritable,current->cap_inheritable);
+ */
+ if (issecure(SECURE_PURE_CAP) ) {
+ cap_clear(bprm->cap_inheritable);
+ cap_clear(bprm->cap_permitted);
+ cap_clear(bprm->cap_effective);
+ }
+ else {
+ /* these are some useful defaults */
+ cap_t(bprm->cap_inheritable)=cap_t(current->cap_permitted);
+ cap_mask(bprm->cap_inheritable,current->cap_inheritable);
+ cap_clear(bprm->cap_permitted);
+ cap_set_full(bprm->cap_effective);
+ }

/* To support inheritance of root-permissions and suid-root
* executables under compatibility mode, we raise the
- * effective and inherited bitmasks of the executable file
+ * effective and permitted bitmasks of suid-root executable files
* (translation: we set the executable "capability dumb" and
- * set the allowed set to maximum). We don't set any forced
- * bits.
+ * set the allowed set to maximum).
*
- * If only the real uid is 0, we only raise the inheritable
- * bitmask of the executable file (translation: we set the
- * allowed set to maximum and the application to "capability
- * smart").
+ * if root executes a non-root-suid file he will not raise
+ * any special privledges. He will however have his effective
+ * set cleared out for backwards compatibility.
*/

- if (!issecure(SECURE_NOROOT)) {
- if (bprm->e_uid == 0 || current->uid == 0)
- cap_set_full(bprm->cap_inheritable);
- if (bprm->e_uid == 0)
+ if (!issecure(SECURE_NOROOT)&&(mode&S_ISUID)) {
+ if (inode->i_uid==0) {
+ cap_set_full(bprm->cap_permitted);
cap_set_full(bprm->cap_effective);
+ }
+ else if (!current->uid)
+ cap_clear(bprm->cap_effective);
+ }
+ else if (!issecure(SECURE_NO_SETUID_FIXUP)&&
+ current->uid==0&&current->euid) {
+ cap_mask(bprm->cap_effective,current->cap_effective);
}

/* Only if pP' is _not_ a subset of pP, do we consider there
@@ -668,7 +695,8 @@
* The formula used for evolving capabilities is:
*
* pI' = pI
- * (***) pP' = fP | (fI & pI)
+ * (***) pP' = fP | (fI & pI & pP)
+ * used to be pP' = fP | (fI & pI )
* pE' = pP' & fE [NB. fE is 0 or ~0]
*
* I=Inheritable, P=Permitted, E=Effective // p=process, f=file
@@ -677,9 +705,14 @@

void compute_creds(struct linux_binprm *bprm)
{
- int new_permitted = cap_t(bprm->cap_permitted) |
- (cap_t(bprm->cap_inheritable) &
- cap_t(current->cap_inheritable));
+ int new_permitted = cap_t(bprm->cap_inheritable) &
+ cap_t(current->cap_inheritable);
+
+ if (!issecure(SECURE_PURE_CAP))
+ cap_mask(new_permitted,current->cap_permitted);
+
+ new_permitted = cap_t(cap_combine(to_cap_t(new_permitted),
+ bprm->cap_permitted)) ;

/* For init, we want to retain the capabilities set
* in the init_task struct. Thus we skip the usual
@@ -688,6 +721,7 @@
cap_t(current->cap_permitted) = new_permitted;
cap_t(current->cap_effective) = new_permitted &
cap_t(bprm->cap_effective);
+ cap_t(current->cap_inheritable) &= ~(cap_t(bprm->cap_disinherite));
}

/* AUD: Audit candidate if current->cap_effective is set */
diff -urP linux/fs/open.c linux-cap/fs/open.c
--- linux/fs/open.c Wed Apr 28 17:40:14 1999
+++ linux-cap/fs/open.c Wed Apr 28 20:44:16 1999
@@ -296,7 +296,7 @@
current->fsgid = current->gid;

/* Clear the capabilities if we switch to a non-root user */
- if (current->uid)
+ if (current->uid||issecure(SECURE_NOROOT))
cap_clear(current->cap_effective);
else
current->cap_effective = current->cap_permitted;
diff -urP linux/include/linux/binfmts.h linux-cap/include/linux/binfmts.h
--- linux/include/linux/binfmts.h Mon Feb 22 14:50:25 1999
+++ linux-cap/include/linux/binfmts.h Wed Apr 28 18:15:29 1999
@@ -25,6 +25,7 @@
struct dentry * dentry;
int e_uid, e_gid;
kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
+ kernel_cap_t cap_disinherite;
int argc, envc;
char * filename; /* Name of binary */
unsigned long loader, exec;
diff -urP linux/include/linux/capability.h linux-cap/include/linux/capability.h
--- linux/include/linux/capability.h Mon Feb 22 14:50:24 1999
+++ linux-cap/include/linux/capability.h Wed Apr 28 18:15:29 1999
@@ -264,6 +264,28 @@

#define CAP_SYS_TTY_CONFIG 26

+/* Allow changes to capability flags associated with files */
+
+#define CAP_SETFCAP 27
+
+/* Allow changing of securebits stuff */
+
+#define CAP_LINUX_SECUREBITS 28
+
+/* These following caps can not really exist right now */
+/* for discussion only */
+/* they are greater than 31 */
+
+/* linux will ignore these. used by user-land only */
+
+#define CAP_LINUX_USR_0 96
+#define CAP_LINUX_USR_15 111
+
+/* reserved for new caps that may be made that normaly everyone has */
+#define CAP_LINUX_RESERVE_NORMAL_0 112
+#define CAP_LINUX_RESERVE_NORMAL_15 127
+
+
#ifdef __KERNEL__

/*
diff -urP linux/include/linux/elf.h linux-cap/include/linux/elf.h
--- linux/include/linux/elf.h Tue Jan 26 15:21:22 1999
+++ linux-cap/include/linux/elf.h Wed Apr 28 18:15:29 1999
@@ -476,6 +476,7 @@
#define NT_PRFPREG 2
#define NT_PRPSINFO 3
#define NT_TASKSTRUCT 4
+#define NT_ELF_CAP 7

/* Note header in a PT_NOTE section */
typedef struct elf32_note {
@@ -495,6 +496,31 @@
Elf32_Word n_descsz; /* Content size */
Elf32_Word n_type; /* Content type */
} Elf64_Nhdr;
+
+/* Capabilities support
+ */
+struct elf_capabilities {
+ Elf32_Word signature; /* 0xca5ab1e */
+ Elf32_Word version;
+/* Currently 0, this is so that you can append on the end painlessly */
+ Elf32_Word flags;
+#define ECF_MAKE_EUID_UID 1
+#define ECF_MAKE_EUID_XUID 2
+ Elf32_Word xuid;
+ Elf32_Word effective; /* fE */
+ Elf32_Word permitted; /* fP */
+ Elf32_Word inheritable; /* fI */
+ Elf32_Word required; /* fR */
+ Elf32_Word known; /* fK */
+ Elf32_Word disinherite; /* fD aka fM */
+ Elf32_Word pad; /* extra padding, pretty thin right now */
+};
+
+struct elf_capabilities_note {
+ Elf32_Nhdr notehdr;
+ Elf32_Word note_signature; /* CAPS 0x43415053 */
+ struct elf_capabilities cap;
+};

#if ELF_CLASS == ELFCLASS32

diff -urP linux/include/linux/securebits.h linux-cap/include/linux/securebits.h
--- linux/include/linux/securebits.h Wed Apr 1 16:26:34 1998
+++ linux-cap/include/linux/securebits.h Wed Apr 28 20:45:54 1999
@@ -1,7 +1,22 @@
#ifndef _LINUX_SECUREBITS_H
#define _LINUX_SECUREBITS_H 1

-#define SECUREBITS_DEFAULT 0x00000000
+#ifndef CONFIG_SECUREBITS_NOROOT
+#define CONFIG_SECUREBITS_NOROOT 0x00000002
+#endif
+
+#ifndef CONFIG_SECUREBITS_NO_SETUID_FIXUP
+#define CONFIG_SECUREBITS_NO_SETUID_FIXUP 0x00000002
+#endif
+
+#ifndef CONFIG_SECUREBITS_PURE_CAP
+#define CONFIG_SECUREBITS_PURE_CAP 0x00000002
+#endif
+
+#define SECUREBITS_DEFAULT ( CONFIG_SECUREBITS_NOROOT | \
+ (CONFIG_SECUREBITS_NO_SETUID_FIXUP << 2) | \
+ (CONFIG_SECUREBITS_PURE_CAP << 4) )
+

extern unsigned securebits;

@@ -17,6 +32,9 @@
to be compatible with old programs relying on set*uid to loose
privileges. When unset, setuid doesn't change privileges. */
#define SECURE_NO_SETUID_FIXUP 2
+
+/* When set, kernel can act in "pure draft" cap mode */
+#define SECURE_PURE_CAP 4

/* Each securesetting is implemented using two bits. One bit specify
whether the setting is on or off. The other bit specify whether the
diff -urP linux/kernel/sys.c linux-cap/kernel/sys.c
--- linux/kernel/sys.c Fri Nov 20 11:43:19 1998
+++ linux-cap/kernel/sys.c Wed Apr 28 18:15:29 1999
@@ -318,6 +318,7 @@
*
* 3) When set*uiding _from_ euid != 0 _to_ euid == 0, the effective
* capabilities are set to the permitted capabilities.
+ * The inheritible set is combined with the permitted.
*
* fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should
* never happen.
@@ -337,6 +338,8 @@
}
if (old_euid != 0 && current->euid == 0) {
current->cap_effective = current->cap_permitted;
+ current->cap_inheritable=cap_combine(current->cap_inheritable,
+ current->cap_permitted);
}
}

--
Warning:
When I mention capabilites I mean "soiled" capabilities not "pure draft".
Any caps I mention are *derived* from a withdrawn draft posix document.

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/