-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hardening xrdp with AppArmor #2265
Conversation
/*****************************************************************************/ | ||
/* returns -1 on error, else return handle or file descriptor */ | ||
int | ||
g_file_open_ro(const char *file_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I like this idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do too. I'm also quite happy with the idea of setting up a g_file_open_rw()
as the special case as you hint at above, @iskunk. If you want to put the extra work in to do it that way, that's fine by me - I can give you a hand checking you haven't missed anything.
sesman/session.c
Outdated
process_is_unconfined = !g_strcmp(buf, "unconfined\n"); | ||
} | ||
#endif | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see why you're doing this this way, but this is a platform-specific test, so I don't think it belongs in-line in this way.
In fact, the prctl()
call shouldn't really be here either - I know that's not your fault.
How about adding a call g_set_no_new_privs()
to common/os_calls.c? Than can be a no-op on anything other than linux, and all the code related to this area can go in that.
Did I explain that well enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @matt335672, I understand your concern. I think an abstracted NoNewPrivileges call is a fine idea.
I'll update this PR, and add in a commit with the g_file_open_rw()
change as well. Allow me a day or so.
@iskunk - first thing I should say is this is a great idea, and we should definitely get something in to support this. My biggest concern is support. For SELinux (which is only really used by Fedora/RHEL and derivatives), we ship xrdp as SELinux-ready, and leave it to the distribution to configure. There are two reasons for this:-
What I would be happy with is getting the product AppArmor ready so that either the distros can use it by adding profiles, or securlty-conscious users with the experience to admin this could easily get it configured. Shipping example profiles would be OK I think, provided they were basic. Anyone else want to comment on this? I'd really like to find the most productive way forward on what could be a great feature. |
PR update pushed. A few notes:
|
Some comments re support:
|
Fixed a thinko in |
Thanks @iskunk I've enabled the CI on the above, part of which is a code formatting check to see if the code more-of-less complies with the coding standard on the wiki. As a hint you can run the You raise an interesting point regarding SELinux which AFAIK doesn't need to disable NNP. It's been a while since I've looked at this though, and I'm not sure I can trust my memory. I'll go away and do a bit of research and get back to you. |
The NNP disable test isn't needed for SELinux - the SELinux policy included with Fedora allows a process with NNP set to pivot to the SELinux While it would be great to have scenario-based testing for releases, we don't have any at the moment - just unit testing for new features. So given we can't regression-test these profiles easily, I don't think we can enable them as part of the base product. I'd b happy to ship examples which the destros could use as a basis - I think the upstreaming point you make is a good one. |
Updated the code formatting, used As I understand it, NNP generally prohibits security transitions on a process because there is no easy way to verify that the process permissions afterward are a subset of those before. However, there is an exception to this when transitioning from the unconfined state, because security restrictions are never going to increase the permissions of an unconfined process. I think that's probably what's going on with the Fedora SELinux config. I suspect that if they were to take an approach similar to the one here (confine both xrdp-sesman and Xorg, but with different rulesets, thereby requiring a transition when exec'ing Xorg), then skipping NNP would likewise become necessary. Please let me know if there's anything else you'd like to see polished further. |
The default Fedora SELinux policy is as basic as you can imagine. Essentially the daemons run as process type Regarding your comment on If particular shops want to introduce a more restrictive policy, by running xrdp through their own use-cases, they are welcome to do so, and can do so by replacing the Thanks for your changes, and the work youve put into this. I'll have a good look and a think when I've got a bit more time than I have today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comments here - see also below
@@ -2159,7 +2162,7 @@ g_memcmp(const void *s1, const void *s2, int len) | |||
/*****************************************************************************/ | |||
/* returns -1 on error, else return handle or file descriptor */ | |||
int | |||
g_file_open(const char *file_name) | |||
g_file_open_rw(const char *file_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code's not visible here, but in g_file_open_rw()
, remove the fallback code for opening the file read-only.
Also, in reply to a conversational comment I think having _ro
and _rw
versions of g_open_file
is a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. The CreateFileA()
call in g_file_open_rw()
will need updating, however (I don't know the API well enough to do this myself) and I noticed that g_file_open_ex()
doesn't work on Windows. So g_file_open_ro()
will probably need a _WIN32
section of its own.
ACK on not renaming g_file_open_ro()
to g_file_open()
.
common/os_calls.c
Outdated
return 0; | ||
#else | ||
/* | ||
* If the process is already confined in some way, then AppArmor/SELinux |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest removing SELinux from this comment, as the code below does not apply to the SELinux case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I see that there is a way for SELinux policies to allow transitions under NNP, so hopefully such a problem in the future could be addressed with a policy tweak rather than checking for SELinux confinement here.
(Incidentally, I've seen mention of development of a way of allowing AppArmor transitions under NNP, so perhaps at some point in the future this check could be removed in favor of a slightly more sophisticated AppArmor policy. Not an option for now, however.)
I've got two significantly larger comments in addition to the minor one above. The first is that AppArmor support is not supported on all our target platforms (some Linux, but also notably FreeBSD), and so needs to be enabled at compile time. That's pretty simple, and I even worked out a patch for it (attached). The second is a bit more of a problem, possibly a showstopper. I was looking at substituting variables in the apparmor.d instfiles. The substitution is easy enough, and renaming the files to match whatever sbindir is set to is also doable. The problem is that the files themselves are way too specific to Debian/Ubuntu in their current form. There's a bunch of stuff in there that we have no real control over, including but not limited to:-
I think these are going to have to be factored out into Distro-specific files somehow (which will not be maintained by us), and it's not at all clear to me how we can do that. |
Re AppArmor support on target platforms: Is a (Also, Re distro-specific bits, yes---the profiles I've submitted are particular to Debian/Ubuntu, as that is what I'm able to provide. Generalizing them to cover other distros will need contributions from those users. The bits that are outside of our control should, in theory, be addressed by the However, while the profiles may not be as simple as they ought to be, the fact that they (AFAIK) exhaustively list the permissions required for the programs to run is useful. It means that a packager need only delete permissions that are made redundant by newer abstractions (or edit paths on existing permissions to match distro conventions) instead of having to hunt for new, unknown permissions. And permissions relating to XDG_DATA_HOME and DBus shouldn't need to be touched at all, as those elements are standardized across distros by freedesktop.org. And of course, if users contribute details of the modifications that are needed to adapt the profiles to their systems, then those can be rolled in so the profiles are ready to use out-of -the-box. To give a simple example, where Debian/Ubuntu use |
I believe the configure option is necessary. We've made a decision as a project to make such things explicit, so that I'll try to be a little bit clearer about my profile concerns. We really don't have the effort available on this project to spend time fixing AppArmor profiles which aren't quite right for users who have made changes to their systems without having the competence to consider the consequences. We've all been there as users ourselves, and I'm happy to help many newbies here (as my posting record will testify). It is however important that anything we merge here doesn't impact our existing maintenance burden, which we are actively trying to reduce at the moment (with over 300 outstanding issues). It follows that anything we ship has to be bullet-proof, either because it's been fully tested by us, or because the distros have decided it's a feature they're happy to enable and make bulletproof themselves. Please don't misunderstand me - I think what you're doing here is a great addition. The generalisations will need to be made before we can ship any live profiles. I'm happy for us to ship an example profile for Ubuntu, but I think even the one we have at the moment is too fragile for this. |
I think a different approach to handling the "AppArmor support" may be more to your liking. I went the "check the
No. 1 would achieve the aim of explicitness, and no. 2 is an alternate solution to the original problem. Neither of these rely on an AppArmor-specific interface. That aside, note that distros that don't enable the AppArmor LSM by default can still have it enabled by users (e.g. Debian used to have an With regards to profile support, I see a few possibilities for how to handle this:
Would some/all of these address your concerns? Having the profiles fully generalized before contributing them is effectively a chicken-and-egg problem. One person could do that only if they regularly work with two or more distros and are able to develop AppArmor profiles from scratch and are specifically interested in xrdp enough to do so. I wouldn't hold my breath. Two or more people could do that, but they need to find each other first---and if the common point of contact (xrdp) doesn't have anything for them, they are more likely to develop their own distro-specific profiles. The opportunity for collaboration will be lost, and the fragmentation won't deliver a better end result. |
That's a well-considered post - thanks. Configuration options; I'm happy with 1). I'd considered 2) as well myself, but discounted it for precisely the reasons you suggest. I find interposing a shell script for an executable is a really handy debugging technique. As regards the profiles, what you suggest is fine, but the README needs to go at the top-level I think so that it's more prominent. It can probably also be linked from the project README.md so it's findable from the description on Github. Maybe a README.apparmor.md? I accept the chicken-and-egg problem is a real one. I'm less sure than you are about the benefits or possibilities of collaboration, but then I'm also less knowledgeable about AppArmor in general! I'm very happy to work with you to get something in here. I've got a few family commitments at the moment which are going to take up quite a lot of my free time. Please don't take any long periods of silence on my part as a lack of interest. |
All right, I think we have a way forward. Please let me know if any of my understanding below is at variance with yours.
"Collaboration" may have been overselling it a bit, but the key point is that interested newcomers build upon the work that has already been done, rather than [feel they have no choice but to] start from scratch. The only thing worse than someone out there wasting time on redundant work is doing that oneself.
Understood; I have my own "extracurriculars" as well. I'll need some more time to get all this together, but it'll come---this is ultimately my own itch I'm scratching! |
I don't think you need to spend time to support Win32. See also Platform Support Tier for the status of Win32. You can focus on Linux (and BSD) rather than taking effort for Win32. |
@metalefty: Understood; thanks for the heads-up. I'll keep my changes POSIX-only. |
@iskunk - that all sounds fine to me, and along the lines I'm thinking. You'll need to add a commented-out setting for your variable to Thanks. |
@matt335672, of course. Here is an early draft for those; let me know if this looks OK: diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in
index 366ecfad..ea6694da 100644
--- a/docs/man/sesman.ini.5.in
+++ b/docs/man/sesman.ini.5.in
@@ -293,6 +293,15 @@ To keep compatibility, the following aliases are also available.
If set to \fB1\fR, \fBtrue\fR or \fByes\fR, require group membership even
if the group specified in \fBTerminalServerUsers\fR doesn't exist.
+.TP
+\fBXorgNoNewPrivileges\fR=\fI[true|false]\fR
+Only applicable on Linux. If set to \fB0\fR, \fBfalse\fR or \fBno\fR, do
+not use the kernel's \fIno_new_privs\fR restriction when invoking the Xorg
+X11 server. The use of \fIno_new_privs\fR is intended to prevent issues due
+to a setuid Xorg executable. However, if a kernel security module (such as
+AppArmor) is used to confine xrdp, \fIno_new_privs\fR may interfere with
+transitions between confinement domains.
+
.SH "X11 SERVER"
Following parameters can be used in the \fB[X11rdp]\fR, \fB[Xvnc]\fR and
\fB[Xorg]\fR sections.
diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in
index 278224a1..bc7aad06 100644
--- a/sesman/sesman.ini.in
+++ b/sesman/sesman.ini.in
@@ -38,6 +38,12 @@ RestrictOutboundClipboard=none
; yes: an alias of all
RestrictInboundClipboard=none
+; On Linux systems, the Xorg X11 server is normally invoked using
+; no_new_privs to avoid problems if the executable is suid. This may
+; interfere with the use of security modules such as AppArmor. Leave this
+; unset unless you need to disable it.
+#XorgNoNewPrivileges=true
+
[Sessions]
;; X11DisplayOffset - x11 display number offset
; Type: integer |
That looks fine to me. You can preview what the manpage will look like with |
Due to the ongoing restructuring of sesman, the AppArmor profiles previously presented here will need significant updates. For now, I'd like to get in just the code changes, which I believe everyone here was amenable to. I've rebased my changes to the current tree, and re-worked the commit chain of this PR accordingly. I'll submit new AppArmor profiles for consideration in a future PR, once xrdp has stabilized. Please let me know if there are any issues. On a related note, a couple of questions for @matt335672:
|
Hi @iskunk As I've mentioned in another PR, CI is now fixed and requires a rebase to get working. I'll address your questions first.
|
Rebased as requested. (Thanks for straightening out those CI failures!)
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very happy with this (as already discussed), but we could do with testing your g_file_open() changes.
Here's a patch to get you started (my wife's out tonight!) Don't feel you need to take it verbatim if you've got a better idea.
diff --git a/tests/common/test_os_calls.c b/tests/common/test_os_calls.c
index 71f0303e..e0f44bbc 100644
--- a/tests/common/test_os_calls.c
+++ b/tests/common/test_os_calls.c
@@ -17,6 +17,9 @@
#define TOP_SRCDIR "."
#endif
+// File for testing ro/rw opens
+#define RO_RW_FILE "./test_ro_rw"
+
/******************************************************************************/
/***
* Gets the number of open file descriptors for the current process */
@@ -199,6 +202,45 @@ START_TEST(test_g_file_get_size__5GiB)
END_TEST
#endif
+/******************************************************************************/
+/* Test we can write to a file which is opened for write */
+START_TEST(test_g_file_rw)
+{
+ const char data[] = "File data\n";
+
+ int fd = g_file_open_rw(RO_RW_FILE);
+ ck_assert(fd >= 0);
+
+ int status = g_file_write(fd, data, sizeof(data) - 1);
+ g_file_close(fd);
+
+ // Assume no signals have occurred
+ ck_assert_int_eq(status, sizeof(data) - 1);
+
+ // Leave file in place for test_g_file_ro
+}
+END_TEST
+
+/******************************************************************************/
+/* Test we can't write to a file which is opened read only */
+START_TEST(test_g_file_ro)
+{
+ const char data[] = "File data\n";
+
+ int fd = g_file_open_ro(RO_RW_FILE);
+ ck_assert(fd >= 0);
+
+ int status = g_file_write(fd, data, sizeof(data) - 1);
+ g_file_close(fd);
+
+ // Write must fail
+ ck_assert_int_lt(status, 0);
+
+ // Tidy-up (not checked)
+ g_file_delete(RO_RW_FILE);
+}
+END_TEST
+
/******************************************************************************/
/* Just test we can set and clear the flag. We don't test its operation */
START_TEST(test_g_file_cloexec)
@@ -463,6 +505,8 @@ make_suite_test_os_calls(void)
tcase_add_test(tc_os_calls, test_g_file_get_size__2GiB);
tcase_add_test(tc_os_calls, test_g_file_get_size__5GiB);
#endif
+ tcase_add_test(tc_os_calls, test_g_file_rw);
+ tcase_add_test(tc_os_calls, test_g_file_ro); // Must follow test_g_file_rw
tcase_add_test(tc_os_calls, test_g_file_cloexec);
tcase_add_test(tc_os_calls, test_g_file_get_open_fds);
tcase_add_test(tc_os_calls, test_g_file_is_open);
Rename g_file_open() to g_file_open_rw(), and add a new g_file_open_ro() call that wraps the common g_file_open_ex(file, 1, 0, 0, 0) idiom. This will make the file access mode more explicit in the code. Change all calls to g_file_open() to the _ro() or _rw() variant as appropriate, and replace g_file_open_ex(file, 1, 0, 0, 0) with the _ro() call. Lastly, add tests for the two new calls to test_os_calls.c (code courteously provided by matt335672).
This helps keep the application code free of platform-specific cruft. Also remove a needless #include<sys/prctl.h> from sesman/session_list.c.
This allows Linux's no_new_privs restriction to be disabled when starting the X server, which may be desirable if xrdp is running inside a kernel confinement framework such as AppArmor or SELinux.
The idea would be for the helper program to ensure that the config-file path starts with e.g.
Gladly! I've rolled in your test code to my first commit, and confirmed that it passes. (I don't have much to add there, not least as these functions are used everywhere, even in the testing code itself...) |
So to summarise:-
We can simplify this to only allowing relative paths, and not allowing '..' to appear in the config file path. I'm still somewhat concerned about a helper program setting a default environment. In general, by the time we get to this stage, the environment will have been set largely by PAM (or other) calls. I can't see any way to generalise this. The restrictions you've mentioned above for the config could be placed on the startwm.sh invocation instead. Doesn't that cover most of your concerns? I'm going to merge this, by-the-way. Thanks again. I think we can continue the above conversation here. |
Pretty much, yes. (Though interpreting a relative config-file path as relative to
Yes, a necessary aspect to this is that the helper needs to throw away whatever environment it gets (or more specifically, when it is invoked by sesman/sesexec, an AppArmor rule will scrub the environment beforehand), so it will need to set up the user environment itself. Is there not a good way to recreate the environment given by
That approach can also work, since the AppArmor rule can be written to allow (direct) invocation of the script with a scrubbed environment. But then
Much obliged :-) I should have done this (submitted the code changes and left the AA profiles pending) a long time ago. |
I don't think there's a good way to reconstruct the PAM environment in another process. I looked into this in quite some detail when I was constructing the updated architecture. PAM implementations rather assume that everything is happening in the one process. So if Can restricting the window manager script to be under $XRDP_CFG_PATH be done by AppArmor directly? I don't suppose you know what other login managers do in this regard? |
Ensuring that the wm script is in the proper directory is doable, but it doesn't get you much, unfortunately. Even if the script is squeaky-clean, it is still a typical shell script, and is thus prone to muckery like the following:
Unless a shell script goes to the trouble of guarding against this---and it's not even clear to me how feasible that is, given the complexity of the shell environment---it's a sitting duck for this line of attack. (And that's not even getting into what you can do with shell functions, That's why scrubbing a potentially compromised environment is critical. Sesexec may run the wm script as root (because root could legitimately be logging in, questionable as that is), and while sesman/sesexec can be confined under AppArmor (limiting what an attacker can do, even as root), the wm script is not. And if the attacker can make the script walk into their chosen program, as root, then it's game over. There may not be a good solution to this, that doesn't break PAM functionality in one way or another. The closest parallel I can think of is OpenSSH, with its privilege separation and sandboxing mechanisms. (If you need a refresher, this article presents a good general overview IMO. Not to say that those mechanisms have always been impregnable, of course.) The |
At present, the 'WM script' can be any executable - it doesn't have to be a script. The code in sesexec just The problem is, how do you clean up an environment that's potentially compromised at that point? I think we agree that's not solveable in general, but for specialist environments it very much is. I agree that s shell script is not as well suited to this as a specific executable would be. Those environments can provide a suitable executable which knows what the environment would be, and can enforce it before running the script. Some distros are moving towards using The Debian practice is on my short-term TODO list. We've already spoken about this internally. |
A security-critical environment could forgo the standard PAM environment setup, do the AppArmor environment scrubbing when executing So, while I'll give up on the idea of secure execution of |
For some time now, I've been wanting to develop AppArmor profiles to confine xrdp and its components while running. Given that the
xrdp
process is open to the network, andxrdp-sesman
runs as root, these are good candidates for security hardening.Code changes
I found that some modifications to the code are necessary, however, to make this feasible. These changes are in my first commit, and they are relatively minor:
The function
g_file_open()
is used in numerous places to read things like configuration files. However, it is also used for writing files (e.g. PID files). The meat of the function, on the POSIX side, is as follows:This "try at first to open the file read/write" approach does not look good in a confinement framework. With AppArmor enabled, I see messages like this in the system log:
At first, this left me scratching my head thinking, "Why is xrdp trying to write to the keyboard config file?" This didn't prevent xrdp from running correctly (as can happen with AppArmor if you're not careful), but security-minded admins would look askance at this.
There is a different call in the xrdp code that is used in a few places, that gives proper read-only access:
The parameters are four flags, requesting any combination of read access, write access, file creation, and file truncation. Most of the time, however, the above combination is used.
The first change I made was to turn all the
g_file_open()
calls that appear to need only read access, to use a proper read-only call. At first, I replaced them with calls like the aboveg_file_open_ex()
example. But I realized that this function call is not very clear as to what it's doing unless you have the function signature in front of you, and it's used in exactly this form numerous times throughout the code. So I added a new functiong_file_open_ro()
that wraps this specific combination, and made my edits use that instead.(Philosophically speaking, it would probably be better to have
g_file_open()
return a read-only descriptor, and add a new function likeg_file_open_rw()
org_file_open_write()
to return a writable one, so that the request for write access is explicit. But that's beyond the scope of this change.)When the X server is executed, xrdp uses (when available) the Linux kernel's "no new privileges" feature to avoid potential fallout from invoking a setuid-root X server binary. This is a good thing to do, from a security perspective---but paradoxically, it gets in the way of further security hardening via AppArmor.
As it happens, the
NoNewPrivs
flag on a process prevents AppArmor from changing its confinement profile at all, even if the destination profile is more restrictive than the source one. What this means for xrdp is that the X server cannot be confined using a different, narrower profile than its parent (xrdp-sesman). If I attempt to do so, I get an error like this in the system log:This is not ideal, because the X server needs access to a lot fewer things than xrdp-sesman---and, in turn, the X server needs a few things that xrdp-sesman can do without. The two processes should not run with the same set of permissions.
I didn't want to get rid of the
PR_SET_NO_NEW_PRIVS
functionality, and I wanted to avoid adding a new configuration option, so what I did was add a check: If AppArmor is loaded, and the process is already confined in some way, then don't setNoNewPrivs
. This is done by examining the content of/proc/self/attr/apparmor/current
. The check is performed in a fail-safe way, so if anything goes wrong, theNoNewPrivs
transition remains in effect.Apparmor profiles
My second commit adds the new profiles. Some notes on these:
These profiles have been developed and tested on Ubuntu.
I tested only with Xorg and Xvnc (specifically Xtigervnc). I don't have the setup to test vnc-any or neutrinordp-any.
My changes are to the latest Git
devel
branch, but I tested by applying them to the Debian 0.9.19 package and compiling it on Ubuntu. (Ubuntu does not have 0.9.19 yet, as of this writing.)I accommodated the FUSE stuff so that users can login/logout without issue, but I have no idea how to test that functionality.
The profile filenames follow the standard AppArmor convention of reflecting the absolute path of the installed binary. That said, the filenames are arbitrary, and are not parsed in any way.
The profiles themselves do contain absolute paths to various binaries, as AppArmor requires. These can be handled by parameterization (see e.g.
@{Xorg}
in theusr.sbin.xrdp-sesman
profile), alternation (AppArmor supports{a,b,c}
syntax in paths), and of course build-time substitutions@like_this@
.The profiles can be enabled by copying them into
/etc/apparmor.d/
, running/etc/init.d/apparmor force-reload
(or rebooting), and restarting xrdp. Any prohibited actions will appear in the system log withapparmor="DENIED"
.The profiles, as written, are in "enforce" mode. If you would like to experiment, you may want to enable them in "complain" mode. This will cause AppArmor to allow the application to do whatever it wants, but still log anything that it would have prohibited in "enforce" mode. All that is needed is to symlink the profiles into the
/etc/apparmor.d/force-complain/
directory, and run/etc/init.d/apparmor force-reload
(or reboot). Then, watch your system log forapparmor="ALLOWED"
messages.(Additionally, any
deny
AppArmor directives should be preceded withaudit
[if not already present] so that violations of same are logged. Normally,deny
directives request "silent deny" behavior.)Further work
Careful testing of the more advanced xrdp functionality will be needed, to ensure that the AppArmor profiles allow the necessary access. I've covered the basic use cases, but of course there are going to be more exotic setups that need to be supported.
Some adaptation to other Linux distributions will likely be needed. My experience is only with Debian/Ubuntu, however, so I'm not in a good position to address this.
There is one major hole in these AppArmor profiles, which will require additional work to address. It is this line in
usr.sbin.xrdp-sesman
:This allows
startwm.sh
to be executed without confinement. The user session cannot run within the tightly-defined profile for xrdp-sesman; in fact, it probably can't run (or at least normally doesn't run) in any AppArmor profile at all. So this line allows that script an "escape hatch" out of the restrictions imposed on xrdp-sesman.Because this is a shell script, however, there is a risk that could allow an attacker to take advantage of the "escape hatch." Rather than explain the issue here, I will link this excellent article that describes it in detail. (This is an old article, hence the Wayback link, but its analysis still reflects the situation today.)
The current versions of the
startwm.sh
script rely on xrdp-sesman setting upPATH
andLANG
variables (usingpam_env
) prior to its invocation. However, in an environment where this script represents a loosening of process confinement, at leastPATH
has to be treated as "tainted" data potentially originating from an attacker.The simplest thing to do would be to put e.g.
PATH=/bin:/usr/bin
at the top of the script, and then set upPATH
/LANG
properly in shell space. A more complex solution would be to have a separate helper program for this: this helper would be executed unconfined, and then it does thepam_env
stuff, readsDefaultWindowManager
or whatever fromsesman.ini
, and finally exec()s the program. (Importantly, this helper cannot take arguments, as they may come from an attacker and AppArmor rules cannot match against those.)I'll be happy to answer any questions, and revise these changes as needed.