* [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness
@ 2019-04-10 16:56 Micah Morton
2019-04-10 17:28 ` Kees Cook
0 siblings, 1 reply; 4+ messages in thread
From: Micah Morton @ 2019-04-10 16:56 UTC (permalink / raw)
To: jmorris, keescook, casey, linux-security-module; +Cc: Jann Horn, Micah Morton
From: Jann Horn <jannh@google.com>
Someone might write a ruleset like the following, expecting that it
securely constrains UID 1 to UIDs 1, 2 and 3:
1:2
1:3
However, because no constraints are applied to UIDs 2 and 3, an attacker
with UID 1 can simply first switch to UID 2, then switch to any UID from
there. The secure way to write this ruleset would be:
1:2
1:3
2:2
3:3
, which uses "transition to self" as a way to inhibit the default-allow
policy without allowing anything specific.
This is somewhat unintuitive. To make sure that policy authors don't
accidentally write insecure policies because of this, let the kernel verify
that a new ruleset does not contain any entries that are constrained, but
transitively unconstrained.
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Micah Morton <mortonm@chromium.org>
---
security/safesetid/securityfs.c | 21 +++++++++++++++++++
.../selftests/safesetid/safesetid-test.c | 4 +++-
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
index 7a08fff2bc14..3ec64487f0e9 100644
--- a/security/safesetid/securityfs.c
+++ b/security/safesetid/securityfs.c
@@ -77,6 +77,23 @@ static void release_ruleset(struct setuid_ruleset *pol)
call_rcu(&pol->rcu, __release_ruleset);
}
+static int verify_ruleset(struct setuid_ruleset *pol)
+{
+ int bucket;
+ struct setuid_rule *rule;
+
+ hash_for_each(pol->rules, bucket, rule, next) {
+ if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
+ SIDPOL_DEFAULT) {
+ pr_warn("insecure policy rejected: uid %d is constrained but transitively unconstrained through uid %d\n",
+ __kuid_val(rule->src_uid),
+ __kuid_val(rule->dst_uid));
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
static ssize_t handle_policy_update(struct file *file,
const char __user *ubuf, size_t len)
{
@@ -139,6 +156,10 @@ static ssize_t handle_policy_update(struct file *file,
goto out_free_buf;
}
+ err = verify_ruleset(pol);
+ if (err)
+ goto out_free_buf;
+
/*
* Everything looks good, apply the policy and release the old one.
* What we really want here is an xchg() wrapper for RCU, but since that
diff --git a/tools/testing/selftests/safesetid/safesetid-test.c b/tools/testing/selftests/safesetid/safesetid-test.c
index 4f03813d1911..8f40c6ecdad1 100644
--- a/tools/testing/selftests/safesetid/safesetid-test.c
+++ b/tools/testing/selftests/safesetid/safesetid-test.c
@@ -144,7 +144,9 @@ static void write_policies(void)
{
static char *policy_str =
"1:2\n"
- "1:3\n";
+ "1:3\n"
+ "2:2\n"
+ "3:3\n";
ssize_t written;
int fd;
--
2.21.0.392.gf8f6787159e-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness
2019-04-10 16:56 [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness Micah Morton
@ 2019-04-10 17:28 ` Kees Cook
2019-04-10 17:36 ` Jann Horn
0 siblings, 1 reply; 4+ messages in thread
From: Kees Cook @ 2019-04-10 17:28 UTC (permalink / raw)
To: Micah Morton
Cc: James Morris, Casey Schaufler, linux-security-module, Jann Horn
On Wed, Apr 10, 2019 at 9:56 AM Micah Morton <mortonm@chromium.org> wrote:
>
> From: Jann Horn <jannh@google.com>
>
> Someone might write a ruleset like the following, expecting that it
> securely constrains UID 1 to UIDs 1, 2 and 3:
>
> 1:2
> 1:3
>
> However, because no constraints are applied to UIDs 2 and 3, an attacker
> with UID 1 can simply first switch to UID 2, then switch to any UID from
> there. The secure way to write this ruleset would be:
>
> 1:2
> 1:3
> 2:2
> 3:3
>
> , which uses "transition to self" as a way to inhibit the default-allow
> policy without allowing anything specific.
>
> This is somewhat unintuitive. To make sure that policy authors don't
> accidentally write insecure policies because of this, let the kernel verify
> that a new ruleset does not contain any entries that are constrained, but
> transitively unconstrained.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> Signed-off-by: Micah Morton <mortonm@chromium.org>
> ---
> security/safesetid/securityfs.c | 21 +++++++++++++++++++
> .../selftests/safesetid/safesetid-test.c | 4 +++-
> 2 files changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
> index 7a08fff2bc14..3ec64487f0e9 100644
> --- a/security/safesetid/securityfs.c
> +++ b/security/safesetid/securityfs.c
> @@ -77,6 +77,23 @@ static void release_ruleset(struct setuid_ruleset *pol)
> call_rcu(&pol->rcu, __release_ruleset);
> }
>
> +static int verify_ruleset(struct setuid_ruleset *pol)
> +{
> + int bucket;
> + struct setuid_rule *rule;
> +
> + hash_for_each(pol->rules, bucket, rule, next) {
> + if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
> + SIDPOL_DEFAULT) {
> + pr_warn("insecure policy rejected: uid %d is constrained but transitively unconstrained through uid %d\n",
> + __kuid_val(rule->src_uid),
> + __kuid_val(rule->dst_uid));
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
Why fail open? How about having the policy automatically add the
constraints (since the entire policy is known at verification time)?
-Kees
> +
> static ssize_t handle_policy_update(struct file *file,
> const char __user *ubuf, size_t len)
> {
> @@ -139,6 +156,10 @@ static ssize_t handle_policy_update(struct file *file,
> goto out_free_buf;
> }
>
> + err = verify_ruleset(pol);
> + if (err)
> + goto out_free_buf;
> +
> /*
> * Everything looks good, apply the policy and release the old one.
> * What we really want here is an xchg() wrapper for RCU, but since that
> diff --git a/tools/testing/selftests/safesetid/safesetid-test.c b/tools/testing/selftests/safesetid/safesetid-test.c
> index 4f03813d1911..8f40c6ecdad1 100644
> --- a/tools/testing/selftests/safesetid/safesetid-test.c
> +++ b/tools/testing/selftests/safesetid/safesetid-test.c
> @@ -144,7 +144,9 @@ static void write_policies(void)
> {
> static char *policy_str =
> "1:2\n"
> - "1:3\n";
> + "1:3\n"
> + "2:2\n"
> + "3:3\n";
> ssize_t written;
> int fd;
>
> --
> 2.21.0.392.gf8f6787159e-goog
>
--
Kees Cook
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness
2019-04-10 17:28 ` Kees Cook
@ 2019-04-10 17:36 ` Jann Horn
2019-04-10 18:17 ` Kees Cook
0 siblings, 1 reply; 4+ messages in thread
From: Jann Horn @ 2019-04-10 17:36 UTC (permalink / raw)
To: Kees Cook
Cc: Micah Morton, James Morris, Casey Schaufler,
linux-security-module
On Wed, Apr 10, 2019 at 7:28 PM Kees Cook <keescook@chromium.org> wrote:
> On Wed, Apr 10, 2019 at 9:56 AM Micah Morton <mortonm@chromium.org> wrote:
> > From: Jann Horn <jannh@google.com>
> >
> > Someone might write a ruleset like the following, expecting that it
> > securely constrains UID 1 to UIDs 1, 2 and 3:
> >
> > 1:2
> > 1:3
> >
> > However, because no constraints are applied to UIDs 2 and 3, an attacker
> > with UID 1 can simply first switch to UID 2, then switch to any UID from
> > there. The secure way to write this ruleset would be:
> >
> > 1:2
> > 1:3
> > 2:2
> > 3:3
> >
> > , which uses "transition to self" as a way to inhibit the default-allow
> > policy without allowing anything specific.
> >
> > This is somewhat unintuitive. To make sure that policy authors don't
> > accidentally write insecure policies because of this, let the kernel verify
> > that a new ruleset does not contain any entries that are constrained, but
> > transitively unconstrained.
> >
> > Signed-off-by: Jann Horn <jannh@google.com>
> > Signed-off-by: Micah Morton <mortonm@chromium.org>
> > ---
> > security/safesetid/securityfs.c | 21 +++++++++++++++++++
> > .../selftests/safesetid/safesetid-test.c | 4 +++-
> > 2 files changed, 24 insertions(+), 1 deletion(-)
> >
> > diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
> > index 7a08fff2bc14..3ec64487f0e9 100644
> > --- a/security/safesetid/securityfs.c
> > +++ b/security/safesetid/securityfs.c
> > @@ -77,6 +77,23 @@ static void release_ruleset(struct setuid_ruleset *pol)
> > call_rcu(&pol->rcu, __release_ruleset);
> > }
> >
> > +static int verify_ruleset(struct setuid_ruleset *pol)
> > +{
> > + int bucket;
> > + struct setuid_rule *rule;
> > +
> > + hash_for_each(pol->rules, bucket, rule, next) {
> > + if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
> > + SIDPOL_DEFAULT) {
> > + pr_warn("insecure policy rejected: uid %d is constrained but transitively unconstrained through uid %d\n",
> > + __kuid_val(rule->src_uid),
> > + __kuid_val(rule->dst_uid));
> > + return -EINVAL;
> > + }
> > + }
> > + return 0;
> > +}
>
> Why fail open? How about having the policy automatically add the
> constraints (since the entire policy is known at verification time)?
Are you suggesting to print a warning, automatically add the
constraint, apply the policy, and still return -EINVAL? I guess that
would work.
I think it is a good idea to require userspace to explicitly write
these rules, since implicitly-added rules might make the rules harder
to understand for someone reading a policy file.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness
2019-04-10 17:36 ` Jann Horn
@ 2019-04-10 18:17 ` Kees Cook
0 siblings, 0 replies; 4+ messages in thread
From: Kees Cook @ 2019-04-10 18:17 UTC (permalink / raw)
To: Jann Horn
Cc: Micah Morton, James Morris, Casey Schaufler,
linux-security-module
On Wed, Apr 10, 2019 at 10:37 AM Jann Horn <jannh@google.com> wrote:
>
> On Wed, Apr 10, 2019 at 7:28 PM Kees Cook <keescook@chromium.org> wrote:
> > On Wed, Apr 10, 2019 at 9:56 AM Micah Morton <mortonm@chromium.org> wrote:
> > > From: Jann Horn <jannh@google.com>
> > >
> > > Someone might write a ruleset like the following, expecting that it
> > > securely constrains UID 1 to UIDs 1, 2 and 3:
> > >
> > > 1:2
> > > 1:3
> > >
> > > However, because no constraints are applied to UIDs 2 and 3, an attacker
> > > with UID 1 can simply first switch to UID 2, then switch to any UID from
> > > there. The secure way to write this ruleset would be:
> > >
> > > 1:2
> > > 1:3
> > > 2:2
> > > 3:3
> > >
> > > , which uses "transition to self" as a way to inhibit the default-allow
> > > policy without allowing anything specific.
> > >
> > > This is somewhat unintuitive. To make sure that policy authors don't
> > > accidentally write insecure policies because of this, let the kernel verify
> > > that a new ruleset does not contain any entries that are constrained, but
> > > transitively unconstrained.
> > >
> > > Signed-off-by: Jann Horn <jannh@google.com>
> > > Signed-off-by: Micah Morton <mortonm@chromium.org>
> > > ---
> > > security/safesetid/securityfs.c | 21 +++++++++++++++++++
> > > .../selftests/safesetid/safesetid-test.c | 4 +++-
> > > 2 files changed, 24 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
> > > index 7a08fff2bc14..3ec64487f0e9 100644
> > > --- a/security/safesetid/securityfs.c
> > > +++ b/security/safesetid/securityfs.c
> > > @@ -77,6 +77,23 @@ static void release_ruleset(struct setuid_ruleset *pol)
> > > call_rcu(&pol->rcu, __release_ruleset);
> > > }
> > >
> > > +static int verify_ruleset(struct setuid_ruleset *pol)
> > > +{
> > > + int bucket;
> > > + struct setuid_rule *rule;
> > > +
> > > + hash_for_each(pol->rules, bucket, rule, next) {
> > > + if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
> > > + SIDPOL_DEFAULT) {
> > > + pr_warn("insecure policy rejected: uid %d is constrained but transitively unconstrained through uid %d\n",
> > > + __kuid_val(rule->src_uid),
> > > + __kuid_val(rule->dst_uid));
> > > + return -EINVAL;
> > > + }
> > > + }
> > > + return 0;
> > > +}
> >
> > Why fail open? How about having the policy automatically add the
> > constraints (since the entire policy is known at verification time)?
>
> Are you suggesting to print a warning, automatically add the
> constraint, apply the policy, and still return -EINVAL? I guess that
> would work.
I meant not return EINVAL in this case but just fix it.
> I think it is a good idea to require userspace to explicitly write
> these rules, since implicitly-added rules might make the rules harder
> to understand for someone reading a policy file.
Mostly it's a question for the userspace design. If EINVAL is checked,
then I think it's probably fine, but will that error propagate to
whatever will eventually launch the processes that are supposed to be
confined? Since it's a detectable case, why not just fix it (with a
warning) and run safely?
--
Kees Cook
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2019-04-10 18:17 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-04-10 16:56 [PATCH 09/10] LSM: SafeSetID: verify transitive constrainedness Micah Morton
2019-04-10 17:28 ` Kees Cook
2019-04-10 17:36 ` Jann Horn
2019-04-10 18:17 ` Kees Cook
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).