Skip to content

Conversation

@WavyEbuilder
Copy link

@WavyEbuilder WavyEbuilder commented Sep 30, 2025

I understand that the issue on this was previously turned down because you understandably don't want to have to maintain SELinux code, but I think it may be worth a second look:

  1. The only SELinux specific pieces of this code are the calls to selinux_check_access (), getcon_raw (), security_getenforce (), and selinux_set_callback (). These are also all stable APIs that have not changed in years; for example, the last change to selinux_check_access () was 11 years ago at the time of writing, and even calls made previously would still succeed due a typedef. Upstream (libselinux) also promises ABI stability.
  2. SO_PEERSEC (the other piece of logic here) is not actually SELinux specific, meaning this code could be trivially changed/be updated to work with various other LSMs also in active use, for example AppArmor and SMACK.
     
    If it's of any reassurance, outside of some personal emergency, I'm available to respond to any bugs fairly quickly. I'm happy to be pinged on any issues created that even involve builds with the SELinux build option if desired. I've run this on my own personal systems for over 6 months now.

I do understand if you still wish to turn it down, however this would be great to have upstream; it would allow a compelling case to work on improving the Wayland security model in reference policy as well.
 
Best Regards,
Rahul

Sidenote: get_selinux_protocol () is a bit ugly, but I'm not sure if there's a nicer way to write this; ->name and trim doesn't seem to work as struct wl_global is opaque :/ - suggestions appreciated.

@kennylevinsen
Copy link
Member

Is there any precedent for this specific use of SELinux, either for Wayland interfaces specifically or similar constructs?

One concern is that we natively support FreeBSD, and we generally write code targeting both platform. This does not rule out ifdefs, but core functionality should be portable, and so if we find arbitrary global filtering rules to be a notable feature we would need a mechanism that also works on FreeBSD.

Likewise, we also support non-SELinux setups, and those should not be without core functionality.

@WavyEbuilder
Copy link
Author

WavyEbuilder commented Sep 30, 2025

Is there any precedent for this specific use of SELinux, either for Wayland interfaces specifically or similar constructs?

Yes. Xorg has XACE, which is actually what I modelled the basic design off of. That uses SELinux to control access to all parts of the X server. SELinux reference policy supports it upstream, and so does the Xorg server.

One concern is that we natively support FreeBSD, and we generally write code targeting both platform. This does not rule out ifdefs, but core functionality should be portable, and so if we find arbitrary global filtering rules to be a notable feature we would need a mechanism that also works on FreeBSD.

I do understand. We have security-context-v1 right now, and I'm not sure if FreeBSD MAC has anything similar to SELinux classes or SO_PEERSEC from Linux. I'd be happy to take a look.

Likewise, we also support non-SELinux setups, and those should not be without core functionality.

This only adds another filtering mechanism on SELinux setups. Other setups can rely on things like security-context-v1, this just allows using the SELinux LSM to do filter globals instead. The same logic using SO_PEERSEC as in the patch can be applied to other LSMs like AppArmor and SMACK as well, although I'm not personally comfortable writing those (as I only have SELinux experience). However, I do know it should be a fairly trivial task for someone familiar with them.

If anything, I'd argue that security-context-v1 needs building out for other platforms. The kind of context-based approach is MAC specific (with huge benefits for systems using MAC), but that isn't trivially if at all replicable on systems without MAC. While a security-context-v1 with specific globals would replicate this functionality in a discretionary way, that still doesn't negate this patch however, which does so in a mandatory way. Part of why this works is because SO_PEERSEC is a guaranteed way to identify a client; the kernel and SELinux are responsible for doing so.

@WavyEbuilder
Copy link
Author

Update:

  • FreeBSD seems to have mac_get_fd () which appears to work in a similar manner to getsockopt () and SO_PEERSEC. However, I can't seem to find a way to query the kernel from userspace to decide whether an action is allowed or denied. I also can't find a mention of any form of custom object classes.
  • AppArmor has aa_query_label () which operates in a similar manner to selinux_check_access (), using the context we acquired using SO_PEERSEC (so at least that part of the code is reusable).

dbus-broker has an example of using AppArmor to query access, although I'm not personally comfortable implementing it (I'm only an SELinux guy, sorry). However, it appears the infrastructure exists should the AppArmor community be interested.

Regardless of FreeBSD's lack of support for this specific form of global filtering, I still think this would be useful to have. I'd personally argue that the filter_global () function is the core "functionality" here:

static bool filter_global(const struct wl_client *client,
		const struct wl_global *global, void *data) {
#if WLR_HAS_XWAYLAND
	struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
	if (xwayland && global == xwayland->shell_v1->global) {
		return xwayland->server != NULL && client == xwayland->server->client;
	}
#endif

	if (!check_access_selinux(global, client)) {
		return false;
	}

	// Restrict usage of privileged protocols to unsandboxed clients
	// TODO: add a way for users to configure an allow-list
	const struct wlr_security_context_v1_state *security_context =
		wlr_security_context_manager_v1_lookup_client(
		server.security_context_manager_v1, (struct wl_client *)client);
	if (is_privileged(global)) {
		return security_context == NULL;
	}

	return true;
}

Right now, we can consider security-context-v1 as the only provider of this functionality. I'd just consider SELinux another provider, except it allows for different flows (hence the desire to be able to use SELinux, this isn't just another way of doing the same thing; right now there is no real way to enforce access "top down" without creating another socket which is complex and convoluted to enforce on all possible processes). security-context-v1 is great, but it's for a different security model. This also gives an alternative to the XACE functionality I mentioned. Here is the SELinux reference policy classes for XACE:
https://github.com/SELinuxProject/refpolicy/blob/main/policy/flask/access_vectors#L454-L598 - it's well used by policy (or at least was, when Xorg was more prevalent).

@WavyEbuilder WavyEbuilder force-pushed the master branch 2 times, most recently from 1d842ee to e66c312 Compare September 30, 2025 22:06
@kennylevinsen
Copy link
Member

right now there is no real way to enforce access "top down" without creating another socket which is complex and convoluted to enforce on all possible processes

Note that using security-contect and creating new sockets is not complex:

way-secure --socket-path $XDG_RUNTIME_DIR/wayland-2 &
WAYLAND_DISPLAY=wayland-2 weston-simple-egl

Enforcement can be done with file ACLs, e.g. by making the original socket inaccessible and either setting the WAYLAND_DISPLAY env var or moving the new socket into the original's spot after setup.


Note that my point for portability was not to discover N different implementation paths - and as there is no precedent for Wayland, having to invent the all these mechanisms - but rather consider if we could have just one, platform-agnostic path. You could, for example, propose an alternate protocol that allows a client to present a listener, accept new connections and only then forward both the connection and a policy decision to the compositor.

However, even if one went with this SELinux-based mechanism, it is something that would require community alignment across display servers for the exact appraoch and interface/protocol/resource naming. It would be a mess if every Wayland client packaged on an SELinux distribution would have to bundle different SELinux policies for every display server it targeted.

This patch exposes privileged globals to SELinux so access to priv
globals can be mediated by SELinux.

Signed-off-by: Rahul Sandhu <[email protected]>
@WavyEbuilder
Copy link
Author

WavyEbuilder commented Sep 30, 2025

Note that using security-contect and creating new sockets is not complex:

It has however the unfortunate need to patch things like launchers. And it gets quite messy in policy as you also need to consider socket "cross contamination" (i.e. you can't simply use the same label for security context sockets as nothing would prevent accessing another socket), and mcs categories have a limit in the number of them that exist, and would also mean that this would not work on policy without mcs support (which there is plenty of).

Note that my point for portability was not to discover N different implementation paths - and as there is no precedent for Wayland, having to invent the all these mechanisms - but rather consider if we could have just one, platform-agnostic path. You could, for example, propose an alternate protocol that allows a client to present a listener, accept new connections and only then forward both the connection and a policy decision to the compositor.

I did actually previously consider and draft this, however it does have an unfortunate disadvantage of there being a window in which before a client could bind to such protocol, during which nothing is checked, leaving a race condition window for which any client could bind to the access checking protocol.

However, even if one went with this SELinux-based mechanism, it is something that would require community alignment across display servers for the exact appraoch and interface/protocol/resource naming. It would be a mess if every Wayland client packaged on an SELinux distribution would have to bundle different SELinux policies for every display server it targeted.

I do agree with this. I'm also the author of the work upstreaming Wayland policy to SELinux reference policy. I'll outline my plan:
If this gets merged, then in reference policy, define the SELinux Wayland class. Regardless of how you tie things in, if you use selinux_check_access anywhere (which is the ideal soloution), regardless of what calls it, you have to provide a class and permission. The class is simply, wayland, and permissions are simply the protocol names. As soon as that is defined in one compositor (which makes it reasonable to be in policy), then any other implementation will use the existing class and names. I don't see this as a huge problem in that regard; the precedent is then set, so other Wayland compositors can simply use the same class name, and they aren't particularly unreasonable (wayland).

@kennylevinsen
Copy link
Member

It has however the unfortunate need to patch things like launchers.

Not really, just do what needs to be done before running the launcher.

I did actually previously consider and draft this, however it does have an unfortunate disadvantage of there being a window in which before a client could bind to such protocol, during which nothing is checked, leaving a race condition window for which any client could bind to the access checking protocol.

It's not much of a race as the startup process is controlled, but there would be ways to solve this if needed.

The class is simply, wayland, and permissions are simply the protocol names.

The patch as it is written right now is not using wayland interface or protocol names for the SELinux "permissions", but the names of sway struct fields.

@WavyEbuilder
Copy link
Author

Not really, just do what needs to be done before running the launcher.

It's not possible to create a socket with a specific context (the important) in advance for something without knowing what is at least the binary path to said socket; I need a specific socket type for each app, e.g. chrome_wayland_socket_t as I can't rely on MCS categories sadly. Without knowing what app will be opened before creating said socket, that isn't really possible.

It's not much of a race as the startup process is controlled

"It's not much of a race as the startup process is controlled" - would you mind elaborating on that? i.e. the exec's sway runs or? In theory could a malicious client not time the window between sway launching and the first exec and bind to said global, or is there something I'm missing?

but there would be ways to solve this if needed.

Would you have any suggestions? Every way I've thought of I've also thought of a workaround for. The problem imo is that Sway needs to know that whatever client binding to a privileged global is trusted, but the reasoning for this patch at present is because Sway doesn't have a way to identify clients as "secure" or "trusted"; there is a way to create lower privileged clients (with security-context-v1), but I don't see a way for Sway to reliably identify a specific client here.

The patch as it is written right now is not using wayland interface or protocol names for the SELinux "permissions", but the names of sway struct fields.

I don't mind changing it to use the specific protocol names, that'd work fine (although a bit messy as they're longer), my earlier point was moreof this patch establishes the precedent.

@kennylevinsen
Copy link
Member

In theory could a malicious client not time the window between sway launching and the first exec

If malicious clients are running before you launch sway, which means before your user session is initiated and therefore before you started any clients, then you have already been compromised with persistent code execution. As this has many other implications that make it unlikely that such system remains safe to use, it might not be relevant to worry about in this context.

If you have not already been compromised with persistent code execution, clients will only start when sway exec's them/you tell them to start, with permissions/sandboxing serving to limit the capabilities of processes you chose to run. The timing here is flexible and entirely controllable.

@WavyEbuilder
Copy link
Author

WavyEbuilder commented Oct 1, 2025

@kennylevinsen Had a discussion with @navi-desu and here is an idea that arose from that discussion:

  • Sway gets an option in the config (launch-time only, similar to the xwayland and primary_selection options) for initialising without creating a default socket. This solves the first problem (the initial socket is privileged);
  • Sway gets an exec --socket or similar option to create a socketpair and set WAYLAND_SOCKET;
  • A new protocol is created to allow socket proxying: the proxying client provides the socket in XDG_RUNTIME_DIR, "accepts" the connection, does whatever access checks it wishes, and sends the connection over to the compositor-proper via fd-passing alongside an array of "allowed" globals.

This solves a number of problems:

  1. No concerns about race conditions at all;
  2. Maximum flexibility for the proxying client to do access checking however it wishes;
  3. No platform-specific code in sway: we don't even need to call SO_PEERSEC ourselves; the proxying client "accepts" the connection first, so it is free to do it. This also allows whatever BSD implementations, as well as implementations with an entirely different paradigm not based around MAC.

Thoughts? If this sounds good overall, I'll start work on a Wayland protocol (in the ext namespace) for this.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants