Re: [PATCH] Smack: Simplified Mandatory Access Control Kernel

Kyle Moffett
2007-08-13T01:38:42Z

On Aug 12, 2007, at 15:41:46, Casey Schaufler wrote:
> --- Kyle Moffett <mrmacman_g4@mac.com> wrote:
>> On Aug 11, 2007, at 21:21:55, Casey Schaufler wrote:
>>> --- Kyle Moffett <mrmacman_g4@mac.com> wrote:
>>>> I was considering compiling the complete list, but such an
>>>> exercise would take me at least an hour to do properly and which
>>>> categories individual permissions should be placed in could be
>>>> argued for weeks.
>>>
>>> I will be happy to consider your arguement when you are willing
>>> to present the complete argument.
>>
>> Here's my complete argument:
>>
>> Regardless of how you categorize "read", "write", "execute", and
>> "doesnt-need-protection" in your policy language, I can write an
>> SELinux policy and a list of labels which expresses that policy.
>> Moreover, without too much work I can probably write a Perl script
>> to do it for you. On the other hand I can only do that if you
>> tell me exactly how you want to categorize those things, though.
>> In my personal opinion they cannot be reasonably categorized
>> without breaking all sorts of tools or leaving gaping holes;
>> precisely the reason that SELinux uses such a fine-grained list.
>
> This identifies an important design philosophy issue, that being
> what granularity is appropriate. I have no interest in composing a
> description of Smack at the granularity that SELinux uses.

If you have no interest in categorizing the SELinux access vectors,
then how do you expect to categorize the LSM hooks, which are almost
1-to-1 mapped with the SELinux access vectors?

> The point you make, that you need to have that in order to create a
> policy description, is one of the reasons for Smack. Simplified.

Well yes, but a simplified policy is useless if it uses no LSM
hooks. I will write you a Perl script which will generate a complete
and functionally equivalent SELinux policy (assuming I have enough
free time) given a file with your policy language. But I can do this
if and only if you tell me which of the SELinux access vectors you
care about (In other words, which of the LSM hooks you want to
require "read", "write", or "execute" privileges for). With such a
little script you could write all the "simplified" policy you want,
without having to change the kernel code at all.

>> Ok, you want sample policy to match your "MLS" sample? For
>> convenience here's one more macro:
>> define(`rx',`r(`$1',`$2') x(`$1',`$2')')
>>
>> type unclass;
>> type c;
>> type s;
>> type ts;
>> rx(c, unclass)
>> rx(s, c)
>> rx(s, unclass)
>> rx(ts, s)
>> rx(ts, c)
>> rx(ts, unclass)
>>
>> In case you don't have the policy you typed into your email, it
>> looks almost identical with a few exceptions for slightly modified
>> syntax:
>>> C Unclass rx
>>> S C rx
>>> S Unclass rx
>>> TS S rx
>>> TS C rx
>>> TS Unclass rx
>
> Yup. Your macro invocations look very much like the Smack
> specification. Your macro definitions are of course unnecessary in
> Smack, and you really ought to be using the MLS labels SELinux
> supports, and you need a base policy underneath this.

My point is your policy format is SO simple it doesn't even need the
SELinux MLS code to handle it. From the way you've described it the
base policy (~200 lines) would be *identical* regardless of the
entries in your policy language, and the rest could be generated by a
script directly from the "C Unclass rx"-type stuff.

>> Whoops, I think I must have smashed the delete key or something
>> while sending. Here's the paragraphs which got elided:
>>
>> Well, yes, but a policy which completely ignores future
>> expandability can't be expanded upon regardless. It would also be
>> very hard to add new policy without a lot of duplication under
>> your system. On the other hand, with SELinux you can very easily
>> add attribute-based policy so adding new capabilities is as simple
>> as sticking existing attributes on newly defined types. For example:
>>
>> type my_log_t, file_type, log_file;
>> type my_log_daemon, daemon;
>>
>> Right there I just gave permission for the logrotate to recycle
>> files labelled my_log_t, which the sysadmin and audit admin can
>> also read (and the audit admin can delete). I also gave
>> permission for my daemon to send SIGCHLD to init, and for init/
>> initscripts to send it a SIGTERM/SIGQUIT. All without writing a
>> SINGLE policy rule. Basically all of those existing behaviors are
>> found in allow rules built on the "file_type", "log_file", and
>> "daemon" attributes.
>
> Ah, now you're refering to the reference policy, right?

Yes, precisely. For most of that functionality there are existing
attributes and types defined in the reference policy to make custom
policy much easier. Furthermore, there are interface files which
allow me to say something like "Let this program spawn an Apache
daemon in the right domain" with a single line. If I only want to do
that when the "httpd" module is loaded I can put the line in an
"optional" block. A policy for a basic network daemon with a couple
log files, a config file, and a little database is all of 30 lines,
maybe 50 if you throw in comments.

>>> They can be added or changed one by one as required while the
>>> system is running, and there are uses that exploit that. One
>>> example is to put the label "Game" on certain programs and:
>>>
>>> at 8:00am "Worker Game no"
>>> at 5:00pm "Worker Game x"
>>>
>>> Thus Worker processes can access Game files only during off hours.
>>
>> This is fundamentally broken:
>> [...]
>> Secondly, you can already do the same thing with DAC and a PAM
>> groups-from-time-of-day module, I don't see why such a thing is
>> special enough to need MAC. Thirdly, I could do exactly the same
>> thing with an SELinux boolean and a cronjob (once we get proper
>> revoke support):
>
> There is usually a way to address any particular problem using DAC,
> it's often sufficiently painful that MAC looks like a better approach.

No, generally the only reason to use MAC is when it's security-
critical (system compromise, classified data, critical
infrastructure, etc). Denying users access to games during the
workday is hardly "security-critical". If that system's CPU time was
exclusively needed for a life support machine during the day then
maybe, but that's what renice or realtime scheduling are for and why
the hell are you installing games on a heart monitor?

> Your boolean solution requires more forthought than the Smack rule
> solution, but I'll give it to you once you've fleshed out your "##"
> lines.

How does it require more forethought? When I want to turn it on, I
write and load the 5 line policy then add the cronjobs. Yours
involves giving cron unconditional permission to write to your
security database (always a bad idea) and then adding similar cronjobs.

>> ## Rule to allow cron to tweak the "can_play_games" boolean value

Hmm, looking at this again the current policy language doesn't have a
way of delegating access to boolean values, so there's no way to let
cron directly poke at booleans right now. You could do it with this
shell script:

#! /bin/sh
now=$(date +'%H%M')
if [ 800 -le "$now" -a 1700 -ge "$now" ]; then
setsebool can_play_games false
else
setsebool can_play_games true
fi

Label that script as games_changer_exec_t then use this bit of policy
type_transition cron_t games_changer_exec_t:process games_changer_t;
can_setbool(games_changer_t);

>> if (can_play_games) {
>> ## Insert game-playing allow rules here
>> }

Once you have written a policy specific to the game you want to allow
or disallow, you would just put that policy inside of the
conditional. As a simple policy example for the ping program (from
an older refpolicy, now a bit out of date, but still useful as an
example):

## The ping domain
type ping_t, domain, privlog;

## Daemons, sysadmins, and users may assume the ping type if allowed
below
role system_r types ping_t;
role sysadm_r types ping_t;
in_user_role(ping_t)

## The ping binary
type ping_exec_t, file_type, sysadmfile, exec_type;

## Allow the sysadmin and init scripts to run ping
domain_auto_trans(sysadm_t, ping_exec_t, ping_t)
domain_auto_trans(initrc_t, ping_exec_t, ping_t)

## Boolean controlling user access to ping
bool user_ping false;
if (user_ping) {
domain_auto_trans(unpriv_userdomain, ping_exec_t, ping_t)
allow ping_t { ttyfile ptyfile }:chr_file rw_file_perms;
}

## Allow ping to do what it needs to do
uses_shlib(ping_t)
can_network(ping_t)
can_ypbind(ping_t)

## Allow additional permissions that ping needs to operate
allow ping_t etc_t:file { getattr read };
allow ping_t self:unix_stream_socket create_socket_perms;
allow ping_t self:rawip_socket { create ioctl read write bind getopt
setopt };
allow ping_t netif_type:netif { rawip_send rawip_recv };
allow ping_t node_type:node { rawip_send rawip_recv };
allow ping_t self:capability { net_raw setuid };

## Allow ping to poke at the terminal
allow ping_t admin_tty_type:chr_file rw_file_perms;
allow ping_t { userdomain privfd kernel_t }:fd use;

## Ping wants to get FS attributes and look in /var but doesn't need to
dontaudit ping_t fs_t:filesystem getattr;
dontaudit ping_t var_t:dir search;

It should be pretty clear what every part of the policy is there for;
basically that's the minimum necessary set of permissions for ping to
run, with an optional boolean to control user pings. There's also a
macro to give your arbitrary process the ability to run ping, such as
for the "heartbeat" daemon for example.

>>> As far as relabeling the file system goes, the rules apply to
>>> processes and objects, not to programs. The label on a program
>>> describes who can access it, it will run with the label of the
>>> process that exec's it. A change in the rules does not require a
>>> change to the labels on the file system for things to work
>>> according to the rules of engagement.
>>
>> By this logic, every program will always have the same label,
>> unless you recode every program to explicitly change it when it
>> starts; precisely the thing that the SELinux type transitions were
>> designed to avoid.
>
> Yes. This is the way it should be. A small set of very carefully
> analysed programs that change labels under carefully controlled
> circumstances is what I want. login, sshd, cron, special purpose
> launchers. Written with the assumption that they will be attacked.

Well, under SELinux, all 3 of those processes go through the special
purpose PAM module to get their labels changed. Are you planning to
modify *every* daemon to have special type-changing code? Hell, most
don't even have setuid/chroot support and that's all of 15 lines of
code and is supported in every UNIX/Linux distro released in the last
10 years (or more). Besides, why allow the program (say, "ping") to
change its own label when the policy could forcibly change it for
you. The label change is NOT just to *give* permissions, it's also
to take them away. For example, when I run "ping", the process gains
raw network access but loses access to almost every user file and
disables LD_PRELOAD, etc, making it a thousand times harder to
compromise it from the inside too.

>>>> When you change the meaning of new types, you clearly would need
>>>> to first reload the policy then relaunch programs or relabel
>>>> files to take advantage of the new types, otherwise they would
>>>> be categorically ignored by the system.
>>>
>>> I'm sorry, but again, I don't know what you mean by "changing the
>>> meaning of new types".
>>
>> Oh, say something like "Oh, drat, I wanted this daemon to run as
>> some other type".
>
> Sorry, "type" is a meaningless attribute in Smack.

Sorry for being unclear. Assume s/type/label/g; the question still
stands.

>> Unless you relabel the files and relaunch the daemon the system
>> will have no idea how the system will change. And if you let
>> arbitrary root processes relabel things on the fly then you've
>> lost all the security advantages to a MAC system.
>
> MAC systems have been behaving this way for decades. SELinux is the
> exception, not the rule on this.

"People have been burning witches for decades, that must be the right
thing to do"

The fact that something is commonly done does not make it right,
especially when there is a significantly more secure alternative
available.

>> I think you may be making assumptions about the behaviour of
>> SELinux based on what other OSes do which don't hold:
>>
>> (a) SELinux does not care about user id, current directory, time
>> of day, etc. It cares solely about 3 things: the Subject, the
>> Object, and the Action. For example, if an init script attempts
>> to execute the apache binary then SELinux gets the following data
>> for parameters: subject="system_u:system_r:initrc_t:SystemLow-
>> SystemHigh" object="system_u:object_r:httpd_exec_t:SystemLow"
>> action="file->execute".
>
> Yes, and each of the components of the subject and object contexts
> is dependent on a more circumstances than I'd care to deal with.
> You've demonstrated that SELinux implements fine granularity.

The beauty of fine granularity is that you can ignore it without
changing code. If you don't want to use the "user" and "role"
portions, then just set them to always be "allusers" and "allroles"
and be done with it. If you don't want MLS then just define one
sensitivity (s0) and no categories. Likewise if want your system to
use only 8 types then only define 8 types. It's just that easy!
Admittedly your policy can't then deal with most of the real-world
situations (like logging in or running the "passwd" command), but
that's your choice.

>> (b) SELinux does NOT have a capability which overrides it. If
>> you want to do something that the policy categorically does not
>> allow, you have 2 choices: change the policy or disable SELinux.
>> You can also use a policy which disables both of those options
>
> Yup. I think that's bad.

Which part do you think is bad? The fact that the policy is
guaranteed to be non-overrideable, or the fact that you can even
prevent changing the security policy from the policy? If you really
want a root-hole in the policy then you can write one, see the
"unconfined_t" in the "targeted" policy.

> And Bell & LaPadula using a combination of levels and categories is
> probably the Smack worst case. But guess what? The dig against
> that policy has always been that no one wanted to combine levels
> and categories.

So how would you secure a system moving data between 3 TS/SCI
compartments and a Secret network? You can say "no one wanted
to...", but we have a couple million dollars worth of business that
says they do.

>> Ok, fine, how does "moving data around a system" require anything
>> more than bog-standard user-level privileges? Normal users under
>> Linux are not permitted to change "security.*" attributes at all,
>> but they may bind to sockets, etc. You can define a "capability"
>> which lets you do all that, but then you're just blindly giving
>> processes more privileges than they need to complete an
>> operation. Why should you get the "rename all files" priv when
>> all you need is "Make my log files get $LABEL when I put them in /
>> var/log/mydaemon/"?
>
> Privilege granularity is a seperate issue. Smack uses Linux
> privilege machanisms and is happy to do so.

"Linux privilege mechanisms" covers no less than 20,000 lines of
code, including DAC, capabilities, LSM, namespaces, keyrings, and
more. Please be more specific here.

>>>> Why should it? Admittedly, moving data from "TopSecret" to
>>>> "Secret" requires a process labeled with a range including at
>>>> least "S-TS" *and* with the MLS "read-to-clearance" attribute,
>>>> but you can be as stingy with that attribute as you like.
>>>
>>> Yup. And plenty of people like you think that is really spiffy.
>>
>> Please explain why you think it isn't?
>
> I grew up believing that downgrading classified information should
> always be an explicit, concious act.

It still is in SELinux too. The only difference is that in SELinux
*all* of the possible ways to downgrade classified information are
listed for you in the policy file for easy analysis; one of the most
common complaints about SUID binaries in UNIX is that they aren't all
listed in one place for easy auditing. Moreover, you can see exactly
how the information may be downgraded. For example, in one policy I
use, I can prove that specific components of our guard software have
permission to move data from a category such as "Secret:NukeTesting"
to just "Secret", but cannot access "TopSecret". I can also verify
that the program itself cannot modify the levels it has access to,
even though it does have permissions to move data between them. It
doesn't make you immune to flaws, but it lets you vastly limit the
damage any given flaw can accomplish.

>> You can call them "process capabilities" or "magic attributes",
>> they're the same thing, just more fine-grained. Instead of (Note:
>> r1 == subject role, r2 == target role, t2 == target type):
>> mlsconstrain process transition ( (r1 == r2) || (t1 ==
>> i_am_a_role_changing_capability) )
>>
>> you have (Note: t1 == subject type):
>> mlsconstrain process transition ( (r1 == r2) || (r1 == system_r
>> && t1 == i_am_a_login_process && t2 == i_am_a_user_entrypoint) )
>>
>> The latter is like the former, except instead of one single
>> ultimate "change-any-role" capability, you have a capability which
>> allows a system login process to change roles *ONLY* if it's also
>> changing to a user entrypoint. Please tell me which you think is
>> more secure.
>
> Don't look at me. A well written program could do fine with either.

The WHOLE POINT of a mandatory access control system is to protect
against *poorly* written programs. If we can verify the kernel (~8
million lines), and the policy (~8,000 lines), then we've saved
ourselves from having to audit all of userspace (~80+ million
lines). Seems a worthy goal to me.

>>>> So the implicit labeling of files actually restricts every
>>>> process *FURTHER*. They no longer have even an inkling of a
>>>> choice about file labeling, and that's a GOOD thing.
>>>
>>> That's true about Smack, you know. The process has no choice
>>> about the label that the files get.
>>
>> So how do you selectively label files based on criteria such as
>> "where did I get put"? How do you distinguish between a "log
>> file" which is create-and-append-only, a "database file" which I
>> can modify however I want to, and a "unix socket" which I can bind
>> in /var/run and get connections from user processes?
>
> I don't. They're all objects.

And by extension you have no way of saying "The apache log files
can't be modified after they are written." That feature is part of
virtually every MAC system anybody's ever made, precisely because you
can't really trust audit logs without it.

>>> Thank you for your comments. I hope that I have made the
>>> behavior and value of Smack clearer in responding to them.
>>
>> You're welcome. I endeavor to poke my nose into every little LSM-
>> related corner in the kernel, this one included.
>
> Why?

Firstly because it's interesting, and secondly because it's related
to my work (which of course comes back to point 1) :-D.

Cheers,
Kyle Moffett