diff mbox

[v3,06/15] commoncap: Refactor to remove bprm_secureexec hook

Message ID 1500416736-49829-7-git-send-email-keescook@chromium.org (mailing list archive)
State New, archived
Headers show

Commit Message

Kees Cook July 18, 2017, 10:25 p.m. UTC
The commoncap implementation of the bprm_secureexec hook is the only LSM
that depends on the final call to its bprm_set_creds hook (since it may
be called for multiple files, it ignores bprm->called_set_creds). As a
result, it cannot safely _clear_ bprm->secureexec since other LSMs may
have set it.  Instead, remove the bprm_secureexec hook by introducing a
new flag to bprm specific to commoncap: cap_elevated. This is similar to
cap_effective, but that is used for a specific subset of elevated
privileges, and exists solely to track state from bprm_set_creds to
bprm_secureexec. As such, it will be removed in the next patch.

Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
moves the bprm_secureexec hook to a static inline. The helper will be
removed in the next patch; this makes the step easier to review and bisect,
since this does not introduce any changes to inputs nor outputs to the
"elevated privileges" calculation.

The new flag is merged with the bprm->secureexec flag in setup_new_exec()
since this marks the end of any further prepare_binprm() calls.

Cc: Serge Hallyn <serge@hallyn.com>
Cc: Andy Lutomirski <luto@kernel.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
---
 fs/exec.c               |  7 +++++++
 include/linux/binfmts.h |  7 +++++++
 security/commoncap.c    | 12 ++++++++----
 3 files changed, 22 insertions(+), 4 deletions(-)

Comments

Andy Lutomirski July 19, 2017, 1:10 a.m. UTC | #1
On Tue, Jul 18, 2017 at 3:25 PM, Kees Cook <keescook@chromium.org> wrote:
> The commoncap implementation of the bprm_secureexec hook is the only LSM
> that depends on the final call to its bprm_set_creds hook (since it may
> be called for multiple files, it ignores bprm->called_set_creds). As a
> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
> have set it.  Instead, remove the bprm_secureexec hook by introducing a
> new flag to bprm specific to commoncap: cap_elevated. This is similar to
> cap_effective, but that is used for a specific subset of elevated
> privileges, and exists solely to track state from bprm_set_creds to
> bprm_secureexec. As such, it will be removed in the next patch.
>
> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
> moves the bprm_secureexec hook to a static inline. The helper will be
> removed in the next patch; this makes the step easier to review and bisect,
> since this does not introduce any changes to inputs nor outputs to the
> "elevated privileges" calculation.
>
> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
> since this marks the end of any further prepare_binprm() calls.

Reviewed-by: Andy Lutomirski <luto@kernel.org>

with the redundant caveat that...

> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1330,6 +1330,13 @@ EXPORT_SYMBOL(would_dump);
>
>  void setup_new_exec(struct linux_binprm * bprm)
>  {
> +       /*
> +        * Once here, prepare_binrpm() will not be called any more, so
> +        * the final state of setuid/setgid/fscaps can be merged into the
> +        * secureexec flag.
> +        */
> +       bprm->secureexec |= bprm->cap_elevated;
> +

...the weird placement of the other assignments to bprm->secureexec
makes this exceedingly confusing.
Kees Cook July 19, 2017, 4:41 a.m. UTC | #2
On Tue, Jul 18, 2017 at 6:10 PM, Andy Lutomirski <luto@kernel.org> wrote:
> On Tue, Jul 18, 2017 at 3:25 PM, Kees Cook <keescook@chromium.org> wrote:
>> The commoncap implementation of the bprm_secureexec hook is the only LSM
>> that depends on the final call to its bprm_set_creds hook (since it may
>> be called for multiple files, it ignores bprm->called_set_creds). As a
>> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
>> have set it.  Instead, remove the bprm_secureexec hook by introducing a
>> new flag to bprm specific to commoncap: cap_elevated. This is similar to
>> cap_effective, but that is used for a specific subset of elevated
>> privileges, and exists solely to track state from bprm_set_creds to
>> bprm_secureexec. As such, it will be removed in the next patch.
>>
>> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
>> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
>> moves the bprm_secureexec hook to a static inline. The helper will be
>> removed in the next patch; this makes the step easier to review and bisect,
>> since this does not introduce any changes to inputs nor outputs to the
>> "elevated privileges" calculation.
>>
>> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
>> since this marks the end of any further prepare_binprm() calls.
>
> Reviewed-by: Andy Lutomirski <luto@kernel.org>
>
> with the redundant caveat that...
>
>> --- a/fs/exec.c
>> +++ b/fs/exec.c
>> @@ -1330,6 +1330,13 @@ EXPORT_SYMBOL(would_dump);
>>
>>  void setup_new_exec(struct linux_binprm * bprm)
>>  {
>> +       /*
>> +        * Once here, prepare_binrpm() will not be called any more, so
>> +        * the final state of setuid/setgid/fscaps can be merged into the
>> +        * secureexec flag.
>> +        */
>> +       bprm->secureexec |= bprm->cap_elevated;
>> +
>
> ...the weird placement of the other assignments to bprm->secureexec
> makes this exceedingly confusing.

Any thoughts on how I could improve this? The main take-away is that
commoncap's secureexec is special, and this was the cleanest way I
could find to deal with it...

-Kees
James Morris July 19, 2017, 9:26 a.m. UTC | #3
On Tue, 18 Jul 2017, Kees Cook wrote:

> The commoncap implementation of the bprm_secureexec hook is the only LSM
> that depends on the final call to its bprm_set_creds hook (since it may
> be called for multiple files, it ignores bprm->called_set_creds). As a
> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
> have set it.  Instead, remove the bprm_secureexec hook by introducing a
> new flag to bprm specific to commoncap: cap_elevated. This is similar to
> cap_effective, but that is used for a specific subset of elevated
> privileges, and exists solely to track state from bprm_set_creds to
> bprm_secureexec. As such, it will be removed in the next patch.
> 
> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
> moves the bprm_secureexec hook to a static inline. The helper will be
> removed in the next patch; this makes the step easier to review and bisect,
> since this does not introduce any changes to inputs nor outputs to the
> "elevated privileges" calculation.
> 
> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
> since this marks the end of any further prepare_binprm() calls.
> 
> Cc: Serge Hallyn <serge@hallyn.com>
> Cc: Andy Lutomirski <luto@kernel.org>
> Signed-off-by: Kees Cook <keescook@chromium.org>


Acked-by: James Morris <james.l.morris@oracle.com>
Andy Lutomirski July 20, 2017, 4:53 a.m. UTC | #4
On Tue, Jul 18, 2017 at 6:10 PM, Andy Lutomirski <luto@kernel.org> wrote:
> On Tue, Jul 18, 2017 at 3:25 PM, Kees Cook <keescook@chromium.org> wrote:
>> The commoncap implementation of the bprm_secureexec hook is the only LSM
>> that depends on the final call to its bprm_set_creds hook (since it may
>> be called for multiple files, it ignores bprm->called_set_creds). As a
>> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
>> have set it.  Instead, remove the bprm_secureexec hook by introducing a
>> new flag to bprm specific to commoncap: cap_elevated. This is similar to
>> cap_effective, but that is used for a specific subset of elevated
>> privileges, and exists solely to track state from bprm_set_creds to
>> bprm_secureexec. As such, it will be removed in the next patch.
>>
>> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
>> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
>> moves the bprm_secureexec hook to a static inline. The helper will be
>> removed in the next patch; this makes the step easier to review and bisect,
>> since this does not introduce any changes to inputs nor outputs to the
>> "elevated privileges" calculation.
>>
>> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
>> since this marks the end of any further prepare_binprm() calls.
>
> Reviewed-by: Andy Lutomirski <luto@kernel.org>
>
> with the redundant caveat that...
>
>> --- a/fs/exec.c
>> +++ b/fs/exec.c
>> @@ -1330,6 +1330,13 @@ EXPORT_SYMBOL(would_dump);
>>
>>  void setup_new_exec(struct linux_binprm * bprm)
>>  {
>> +       /*
>> +        * Once here, prepare_binrpm() will not be called any more, so
>> +        * the final state of setuid/setgid/fscaps can be merged into the
>> +        * secureexec flag.
>> +        */
>> +       bprm->secureexec |= bprm->cap_elevated;
>> +
>
> ...the weird placement of the other assignments to bprm->secureexec
> makes this exceedingly confusing.

Can you just put the bprm->secureexec |=
security_bprm_secureexec(bprm); assignment in prepare_binprm() right
after security_bprm_set_creds()? This would make patch 1 make sense
and make this make sense too, I think.  Or is there some reason why it
wouldn't work?  If the latter, I think the patch descriptions and
comments should maybe be fixed up.
Kees Cook July 31, 2017, 10:43 p.m. UTC | #5
On Wed, Jul 19, 2017 at 9:53 PM, Andy Lutomirski <luto@kernel.org> wrote:
> On Tue, Jul 18, 2017 at 6:10 PM, Andy Lutomirski <luto@kernel.org> wrote:
>> On Tue, Jul 18, 2017 at 3:25 PM, Kees Cook <keescook@chromium.org> wrote:
>>> The commoncap implementation of the bprm_secureexec hook is the only LSM
>>> that depends on the final call to its bprm_set_creds hook (since it may
>>> be called for multiple files, it ignores bprm->called_set_creds). As a
>>> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
>>> have set it.  Instead, remove the bprm_secureexec hook by introducing a
>>> new flag to bprm specific to commoncap: cap_elevated. This is similar to
>>> cap_effective, but that is used for a specific subset of elevated
>>> privileges, and exists solely to track state from bprm_set_creds to
>>> bprm_secureexec. As such, it will be removed in the next patch.
>>>
>>> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
>>> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
>>> moves the bprm_secureexec hook to a static inline. The helper will be
>>> removed in the next patch; this makes the step easier to review and bisect,
>>> since this does not introduce any changes to inputs nor outputs to the
>>> "elevated privileges" calculation.
>>>
>>> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
>>> since this marks the end of any further prepare_binprm() calls.
>>
>> Reviewed-by: Andy Lutomirski <luto@kernel.org>
>>
>> with the redundant caveat that...
>>
>>> --- a/fs/exec.c
>>> +++ b/fs/exec.c
>>> @@ -1330,6 +1330,13 @@ EXPORT_SYMBOL(would_dump);
>>>
>>>  void setup_new_exec(struct linux_binprm * bprm)
>>>  {
>>> +       /*
>>> +        * Once here, prepare_binrpm() will not be called any more, so
>>> +        * the final state of setuid/setgid/fscaps can be merged into the
>>> +        * secureexec flag.
>>> +        */
>>> +       bprm->secureexec |= bprm->cap_elevated;
>>> +
>>
>> ...the weird placement of the other assignments to bprm->secureexec
>> makes this exceedingly confusing.
>
> Can you just put the bprm->secureexec |=
> security_bprm_secureexec(bprm); assignment in prepare_binprm() right
> after security_bprm_set_creds()? This would make patch 1 make sense
> and make this make sense too, I think.  Or is there some reason why it
> wouldn't work?  If the latter, I think the patch descriptions and
> comments should maybe be fixed up.

Yeah, I'll make this change for the next version. It makes things a
little less ugly in the series. In this version I was trying to focus
on eliminating the LSM hook instead of first moving it (to
setup_new_exec()) and then moving it a second time (to the
bprm_set_creds() hook).

Have you had a chance to review the later consolidation patches? So
far no one else has reviewed those. (David, any chance you have some
time too?) I'd love to get at least some Reviewed-bys for them...

-Kees
Andy Lutomirski Aug. 1, 2017, 1:12 p.m. UTC | #6
On Mon, Jul 31, 2017 at 3:43 PM, Kees Cook <keescook@chromium.org> wrote:
> On Wed, Jul 19, 2017 at 9:53 PM, Andy Lutomirski <luto@kernel.org> wrote:
>> On Tue, Jul 18, 2017 at 6:10 PM, Andy Lutomirski <luto@kernel.org> wrote:
>>> On Tue, Jul 18, 2017 at 3:25 PM, Kees Cook <keescook@chromium.org> wrote:
>>>> The commoncap implementation of the bprm_secureexec hook is the only LSM
>>>> that depends on the final call to its bprm_set_creds hook (since it may
>>>> be called for multiple files, it ignores bprm->called_set_creds). As a
>>>> result, it cannot safely _clear_ bprm->secureexec since other LSMs may
>>>> have set it.  Instead, remove the bprm_secureexec hook by introducing a
>>>> new flag to bprm specific to commoncap: cap_elevated. This is similar to
>>>> cap_effective, but that is used for a specific subset of elevated
>>>> privileges, and exists solely to track state from bprm_set_creds to
>>>> bprm_secureexec. As such, it will be removed in the next patch.
>>>>
>>>> Here, set the new bprm->cap_elevated flag when setuid/setgid has happened
>>>> from bprm_fill_uid() or fscapabilities have been prepared. This temporarily
>>>> moves the bprm_secureexec hook to a static inline. The helper will be
>>>> removed in the next patch; this makes the step easier to review and bisect,
>>>> since this does not introduce any changes to inputs nor outputs to the
>>>> "elevated privileges" calculation.
>>>>
>>>> The new flag is merged with the bprm->secureexec flag in setup_new_exec()
>>>> since this marks the end of any further prepare_binprm() calls.
>>>
>>> Reviewed-by: Andy Lutomirski <luto@kernel.org>
>>>
>>> with the redundant caveat that...
>>>
>>>> --- a/fs/exec.c
>>>> +++ b/fs/exec.c
>>>> @@ -1330,6 +1330,13 @@ EXPORT_SYMBOL(would_dump);
>>>>
>>>>  void setup_new_exec(struct linux_binprm * bprm)
>>>>  {
>>>> +       /*
>>>> +        * Once here, prepare_binrpm() will not be called any more, so
>>>> +        * the final state of setuid/setgid/fscaps can be merged into the
>>>> +        * secureexec flag.
>>>> +        */
>>>> +       bprm->secureexec |= bprm->cap_elevated;
>>>> +
>>>
>>> ...the weird placement of the other assignments to bprm->secureexec
>>> makes this exceedingly confusing.
>>
>> Can you just put the bprm->secureexec |=
>> security_bprm_secureexec(bprm); assignment in prepare_binprm() right
>> after security_bprm_set_creds()? This would make patch 1 make sense
>> and make this make sense too, I think.  Or is there some reason why it
>> wouldn't work?  If the latter, I think the patch descriptions and
>> comments should maybe be fixed up.
>
> Yeah, I'll make this change for the next version. It makes things a
> little less ugly in the series. In this version I was trying to focus
> on eliminating the LSM hook instead of first moving it (to
> setup_new_exec()) and then moving it a second time (to the
> bprm_set_creds() hook).
>
> Have you had a chance to review the later consolidation patches? So
> far no one else has reviewed those. (David, any chance you have some
> time too?) I'd love to get at least some Reviewed-bys for them...

I looked briefly.  I'll try to look more closely tomorrow.

--Andy
diff mbox

Patch

diff --git a/fs/exec.c b/fs/exec.c
index 925c85a45d97..53ffa739fb58 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1330,6 +1330,13 @@  EXPORT_SYMBOL(would_dump);
 
 void setup_new_exec(struct linux_binprm * bprm)
 {
+	/*
+	 * Once here, prepare_binrpm() will not be called any more, so
+	 * the final state of setuid/setgid/fscaps can be merged into the
+	 * secureexec flag.
+	 */
+	bprm->secureexec |= bprm->cap_elevated;
+
 	arch_pick_mmap_layout(current->mm);
 
 	/* This is the point of no return */
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 36be5a67517a..a82f5edf23f9 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -35,6 +35,13 @@  struct linux_binprm {
 				 * false if not; except for init which inherits
 				 * its parent's caps anyway */
 		/*
+		 * True if most recent call to the commoncaps bprm_set_creds
+		 * hook (due to multiple prepare_binprm() calls from the
+		 * binfmt_script/misc handlers) resulted in elevated
+		 * privileges.
+		 */
+		cap_elevated:1,
+		/*
 		 * Set by bprm_set_creds hook to indicate a privilege-gaining
 		 * exec has happened. Used to sanitize execution environment
 		 * and to set AT_SECURE auxv for glibc.
diff --git a/security/commoncap.c b/security/commoncap.c
index 7abebd782d5e..abb6050c8083 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -481,6 +481,8 @@  static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_c
 	return rc;
 }
 
+static int is_secureexec(struct linux_binprm *bprm);
+
 /**
  * cap_bprm_set_creds - Set up the proposed credentials for execve().
  * @bprm: The execution parameters, including the proposed creds
@@ -614,11 +616,14 @@  int cap_bprm_set_creds(struct linux_binprm *bprm)
 	if (WARN_ON(!cap_ambient_invariant_ok(new)))
 		return -EPERM;
 
+	/* Check for privilege-elevated exec. */
+	bprm->cap_elevated = is_secureexec(bprm);
+
 	return 0;
 }
 
 /**
- * cap_bprm_secureexec - Determine whether a secure execution is required
+ * is_secureexec - Determine whether a secure execution is required
  * @bprm: The execution parameters
  *
  * Determine whether a secure execution is required, return 1 if it is, and 0
@@ -627,9 +632,9 @@  int cap_bprm_set_creds(struct linux_binprm *bprm)
  * The credentials have been committed by this point, and so are no longer
  * available through @bprm->cred.
  */
-int cap_bprm_secureexec(struct linux_binprm *bprm)
+static int is_secureexec(struct linux_binprm *bprm)
 {
-	const struct cred *cred = current_cred();
+	const struct cred *cred = bprm->cred;
 	kuid_t root_uid = make_kuid(cred->user_ns, 0);
 
 	if (!uid_eq(cred->uid, root_uid)) {
@@ -1079,7 +1084,6 @@  struct security_hook_list capability_hooks[] __lsm_ro_after_init = {
 	LSM_HOOK_INIT(capget, cap_capget),
 	LSM_HOOK_INIT(capset, cap_capset),
 	LSM_HOOK_INIT(bprm_set_creds, cap_bprm_set_creds),
-	LSM_HOOK_INIT(bprm_secureexec, cap_bprm_secureexec),
 	LSM_HOOK_INIT(inode_need_killpriv, cap_inode_need_killpriv),
 	LSM_HOOK_INIT(inode_killpriv, cap_inode_killpriv),
 	LSM_HOOK_INIT(mmap_addr, cap_mmap_addr),