Extending on André's answer, which is the one I think you should accept.
Privilege reduction
The first step is to identify the minimal privileges needed for your app's workflow. You need to manipulate virtual volumes, from what I understand. Can you configure a new UID to do that, without a login shell and without other root
capabilities? Most Linux interfaces have config files for privilege granting, so check with the developers of the commands you need to use.
If you can create a separate UID
Then make one. Make your app run as that. Make it so that there is no bash and home for that UID, so if the app gets exploited there should be no area of your hard drive with permanent write privileges, as much as possible. Also, no opportunity for remote login on that UID. You can have a launcher script (using Upstart, Systemd, Init, whatever, not getting into those polemics) to run your app with this account ID.
If you have to use the root UID
Then understand there's more to root
than it looks like. Firstly, a process that runs as root can give up some privileges which are considered dangerous to hold. Those are called Linux capabilities. So, write a wrapper for your app that drops all irrelevant capabilities. If you need to leave some capabilities, do your research to ensure they cannot be exploited! For instance, Taking Advantage of Linux Capabilities discusses how to re-gain a full capability set starting with only specific capabilities.
Compartmentalisation
See André's answer. Basically, have a tiny app do all the privileged operations so that (1) it's easy to review, you just need to validate the API and perform proper input domain validation; (2) there is less code shipped, thus less chances of exploitable bugs that completely surrender control of your app.
Hardening
Another weapon that was designed to protect you in this kind of scenarios is Mandatory Access Control Linux Security Modules. By default, Linux allows root
to overwrite most Discretionary Access Control checks made on system calls. However, LSMs can still apply their own mandatory logic on calls. This allows LSMs to reduce root
's privileges.
I could talk about AppArmor or TOMOY Linux but let's leave the elementary school toys to the toddlers. You need to look into SELinux, and actually understand it (damn. Yes, it's hard). You need to create a SELinux role, which you will apply to your process. Roles come on top of identities in SELinux to apply policies on all system calls. Roles mean that you can have a root
user which is forbidden from doing virtually anything on the system. Here, you need to identify the system calls that you need (you can use audit2allow
to this end) and grant the role access to those system calls.
Once you've got a list of privileges, make sure to review them, and if possible to only allow them on the appropriate resources. For instance a role may be allowed to write files on /var/www but not /var/log. Which means you'll probably need to label some files on your system with a specific type, and write your own scripts to help you maintain labels. You'll also need to create a new domain for your app, so that you can write what's called a Domain and Type Enforcement policy. The policy will dictate how your app in its domain can only interact with specific other types and domains.
To summarise, the domain-type policy will ensure your process is restrained, and the role policy will ensure your UID is restrained. There is some overlap but both are necessary because you might make a mistake (or have a legitimate need) leading to the app transiting to another, less restricted domain. The SELinux role you impose on your app will ensure that transitions can only occur towards domains of your chosing.
It is still your responsibility to ensure that all the domains allowed for your roles have low enough privileges not to permanently compromise your system. This is easily achieved if (1) you prevent any of the 'exploitable' Linux capabilities; (2) you do not have shared resources between the confined app and other apps; (and (3) you deny any form of permanent storage (see the memoryless property in A Note on the Confinement Problem), so that a compromised app does not persist).
If you can't achieve that, the odds are you can't secure your system because the app just requires too much privilege. That's when it's time to hire experts so they can check that you didn't miss something!