On 7/19/2019 5:15 AM, Simon McVittie wrote:
On Thu, 18 Jul 2019 at 09:13:52 -0700, Casey Schaufler wrote:
> We have discussed what's currently being
> called the "hideous" format, selinux='a:b:c:d',apparmor='x'
which
> in the past, and concluded that the compatibility issues would be too
> great.
I agree this might be too big a compat break for existing interfaces that
were designed with the assumption that there can only be one "big" LSM
at a time, like /proc/54321/attr/current and SO_PEERSEC. It would certainly
break the current libapparmor, and presumably libselinux as well.
However, I think it would be great to have multiple-"big"-LSM-aware
replacements for those interfaces, which present the various LSMs as
multiple parallel credentials.
Defining what would go into liblsm* is a task that has fallen to
the chicken/egg paradox. We can't really define how the user-space
should work without knowing how the kernel will work, and we can't
solidify how the kernel will work until we know what user-space
can use.
---
* I absolutely refuse to allow this to be libsecurity!
I think it would also be valuable to take this opportunity to pin
down
what can and can't be in a label, to an extent where people who want
to represent them in a similar encoding know what they can and can't
assume about their format. For example, when dbus-daemon reports an
unusual event (like rejecting a message due to policy rules or LSMs,
or hitting a resource limit that isn't normally meant to be reached),
the log entry contains miscellaneous information about the process for
debugging purposes, and it would be good if we could include all the LSM
labels in that string without ambiguity. This is essentially the same
problem that the audit subsystem has, but with fewer constraints, since
the audit subsystem has to meet externally-imposed security requirements
but our equivalent is just a nice-to-have for debugging.
Sounds like the Hideous format, or a variant thereof, would be
fine for you, especially if you never parse it.
> Have you been following the discussions on setting a
"display" value
> to specify which LSM data is presented by /proc/self/attr/current and
> SO_PEERSEC? Briefly, a process can write the name of the LSM it wants
> to see data from to /proc/self/attr/display, and the aforementioned
> interfaces will use that LSM. If no value has been set the first LSM
> registered that uses any of these interfaces gets the nod.
I'm vaguely aware of the discussion, but LSMs aren't a big part of my
D-Bus maintainer role, so I'm afraid I can't keep up with all of it.
Do you mean that if process 11111 writes (for example) "apparmor" into
/proc/11111/attr/display, and then reads /proc/22222/attr/current
or queries the SO_PEERSEC of a socket opened by process 22222,
it will specifically see 22222's AppArmor label and not 22222's SELinux
label? Or is the contents of /proc/22222/attr/current controlled
by /proc/22222/attr/display?
Process 11111 would see the AppArmor label when reading
/proc/22222/attr/current. The display value is controlled
by process 11111 so that it can control what data it wants
to see.
How is this meant to work for generic LSM-aware user-space processes?
If
(for example) ps -Z 22222 wants to get both the AppArmor label and the
SELinux label for process 22222, is it meant to write "apparmor" into
attr/display, then read /proc/22222/attr/current, then write "selinux"
to attr/display, then read /proc/22222/attr/current again? That sounds
risky if another thread might be manipulating attr/display concurrently.
The display is set at the task level, so should be thread safe.
The D-Bus message bus/broker (reference implementation: dbus-daemon)
is somewhat tricky because it is returning data on behalf of processes
other than itself, so it would be difficult for it to choose a good
value for "display": there's no reason why it wouldn't be responding
to requests from NetworkManager that expect to see SELinux labels, and
also requests from lxd that expect to see AppArmor labels. Obviously
it can't put both in LinuxSecurityLabel without the same compatibility
issues you're discussing.
Just so.
Also note that dbus-daemon is trusted but mostly unprivileged - it
starts as root, then drops privileges to a system user normally called
messagebus, dbus or _dbus for its normal operation (although it does
retain CAP_AUDIT_WRITE) - so it can't carry out privileged operations
on other processes' /proc entries, if that's what the API requires.
Writing to display does not require privilege, as it affects only
the current process. The display is inherited on fork and reset on
a privileged exec.
I would strongly prefer it if we could get this information from
the kernel in a way that is Linux-specific but LSM-agnostic, without
having to link to libapparmor, libselinux, libsmack and everyone else's
favourite LSM library. At the moment we only need to link to libraries
for the LSMs where dbus-daemon can carry out mediation (asking the LSM
whether to accept or reject messages), and we don't need the libraries
if we are just passing through identity information.
I can see that making dbus-daemon have to decide which label of many
to pass on to its clients would be bad.
I would also prefer it if we can get this information from
SO_PEERSEC
(or some newer SO_PEERSEC replacement) without having to manipulate
ambient/implicit state like attr/display; but dbus-daemon is
single-threaded, so if we must do that, it wouldn't be *so* horrible.
An option that hasn't been discussed is a display option to provide
the Hideous format for applications that know that's what they want.
Write "hideous" into /proc/self/attr/display, and from then on you
get selinux='a:b:c:d',apparmor='z'. This could be used widely in liblsm
interfaces.
Ideally I would like to be able to get all the LSM labels in O(1)
syscalls. Perhaps something with the same (buffer,length) kernel <->
user-space API as SO_PEERSEC and SO_PEERGROUPS, but instead of returning
a single \0-terminated string, it could return either the "hideous" format,
or a byte-blob that looks something like this?
char buffer[ENOUGH_LENGTH] = { 0 };
socklen_t len = sizeof (buffer);
char[] expected =
"apparmor=unconfined\0"
"selinux=system_u:system_r:init_t:s0\0"
"\0"
;
getsockopt (fd, SOL_SOCKET, SO_PEERSECLABELS, &buffer, &length);
/* should return 0 */
/* now buffer should have the same bytes as expected, ending with
* "\0\0" */
(Obviously in real life you'd have a retry loop to get the length right,
like the SO_PEERSEC code in dbus does.)
I would see creating a friendly interface like this as part of
my mythical liblsm, but I see your point.
Because GetConnectionCredentials() is extensible, if there is some
way
to enumerate all the security labels and get their values individually,
we could have (pseudocode)
GetConnectionCredentials(":1.1") -> {
"UnixUserID": 0,
"ProcessID": 1,
"LinuxSecurityLabel.apparmor": "unconfined",
"LinuxSecurityLabel.selinux": "system_u:system_r:init_t:s0",
"LinuxSecurityLabel": "unconfined", /* deprecated */
}
or (using D-Bus' structured type system)
GetConnectionCredentials(":1.1") -> {
"UnixUserID": 0,
"ProcessID": 1,
"LinuxSecurityLabels": {
"apparmor": "unconfined",
"selinux": "system_u:system_r:init_t:s0",
},
"LinuxSecurityLabel": "unconfined", /* deprecated */
}
with LinuxSecurityLabel showing the first LSM registered for backwards
compatibility? Or we could make LinuxSecurityLabel always be in the
"hideous" format if you chose to go that way in the kernel interfaces:
it's defined in terms of SO_PEERSEC, so whatever you do at the kernel
level, D-Bus should mimic that.
If providing the Hideous format makes library code easier or more
efficient I'm happy to make that happen. It can't be the default due to
backward compatibility, but it can be easy as
"echo hideous > /proc/self/attr/display".
smcv