Skip to content
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

Use SystemCallFilter in systemd unit #20632

Closed
wants to merge 1 commit into from

Conversation

iskunk
Copy link

@iskunk iskunk commented Mar 28, 2024

This makes use of systemd's system-call filtering facility to add (Linux) seccomp support to qbittorrent-nox in a non-invasive way. This implicitly also enables NoNewPrivileges. (See SystemCallFilter= in systemd.exec(5) for details.)

The syscall filter marks wide swaths of the Linux kernel API as off-limits, significantly reducing the attack surface of the system available to a baddie who manages to compromise the application. If a prohibited syscall is used, the process is immediately killed with SIGSYS.

The specific filter I've put together here is fairly broad, making use of systemd's syscall sets. It could probably be narrowed down a bit, but should be good as a starting point. I've tested it by running through a torrent download, but as I'm not yet familiar with the application's more advanced features, I've not checked that those can be used without issue. Some testing by a "power user" would be beneficial.

Some notes on testing the filter:

  • If/when the process uses a prohibited syscall, in addition to the SIGSYS, a message like the following will appear in syslog:
    May 21 00:17:10 darkstar kernel: [ 4789.893316] audit: type=1326 audit(1684642630.723:258): auid=4294967295 uid=124 gid=130 ses=4294967295 subj=unconfined pid=28046 comm="qbittorrent-nox" exe="/usr/bin/qbittorrent-nox" sig=31 arch=c000003e syscall=99 compat=0 ip=0x7fb9ee316f9b code=0x80000000
    
  • The syscall=99 bit is salient. The syscall number can be looked up using the scmp_sys_resolver(1) utility:
    $ scmp_sys_resolver 99
    sysinfo
    
  • The syscall name can either be added to the filter directly, or if you'd rather use systemd's syscall sets, the sets can be listed with systemd-analyze syscall-filter.
  • Alternately, you can test without the threat of SIGSYS by using SystemCallLog=. Comment out SystemCallFilter=, and append its arguments to SystemCallLog=~@default. Then watch syslog for messages like the one above (only they will show sig=0).

This reduces the (Linux) kernel attack surface area that may be
exposed if the application is compromised. See "SystemCallFilter="
in systemd.exec(5) for details.
@@ -10,6 +10,8 @@ PrivateTmp=false
User=%i
ExecStart=@EXPAND_BINDIR@/qbittorrent-nox
TimeoutStopSec=1800
SystemCallArchitectures=native
SystemCallFilter=@basic-io @file-system @io-event @network-io @process @signal @sync @system-service @timer
Copy link
Member

@Chocobo1 Chocobo1 Mar 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qbt provides "Run external program" feature. You can find it in Options -> Downloads tab. The external program is run as a child process of qbt. Won't limiting syscalls affect this feature?
If true, we cannot ship this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the syscall filtering would apply to child processes as well.

It's not desirable to have the same piece that interacts with the network also be able to execute arbitrary programs, as that can allow remote access to an attacker. Has there been discussion on something like a privilege-separated architecture, that would make it easier to sandbox the network-facing bits?

Copy link
Member

@Chocobo1 Chocobo1 Mar 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not desirable to have the same piece that interacts with the network also be able to execute arbitrary programs

Maybe for you and some, but others would need the feature. It is quite popular.

Has there been discussion on something like a privilege-separated architecture, that would make it easier to sandbox the network-facing bits?

No. And how would such architecture making it more secure and not affecting the 'Run external program' feature? AFAIK, the external program would still need sufficient privileges to get things done (automation, user scripts, etc).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you only need to download and not doing anything fancy, then I suggest you look into pure downloaders such as aira2. qbt is much more than that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that a privilege-separated architecture does not rule out external program execution. It is about partitioning the application in such a way that the part most likely to be compromised is sandboxed, so that even if an attacker makes it in, they can't break out of it (or at least it's much harder to do so).

This is how modern Web browsers are structured. The part that parses and renders the HTML/CSS/JS is sandboxed, and it communicates with the main browser process (e.g. the GUI, file/print backends and such) through a limited channel (like a socket or shmem interface) that is much simpler than what's going on inside the sandbox.

Similarly, you could have a main daemon, that reads the QBT config, writes the output files, and so on---but to actually talk the BT protocol, you start a separate "net client" process. This client process does all the network I/O, and sends downloaded blocks to the daemon, as well as current status and such. It also receives commands from the daemon, like if you want to pause/stop the download.

The client is sandboxed up the wazoo, but the daemon is not. Now, here's the critical part: When the download is complete, the client piece doesn't run the program itself. (It literally can't.) Instead, it tells the daemon that the download is complete, and the daemon runs the program.

The client doesn't even tell the daemon what program to run, because that information could potentially be coming from an attacker, if they've compromised this part. The channel between the client and daemon has to be kept simple and robust, because in the worst-case scenario, that channel could be under hostile control. (When the parent of a sandbox is compromised via the sandbox communication channel, that is known as a sandbox escape. It turns up in the big Web browsers every now and then.)

That's the basic idea. (Please let me know if I failed to explain it clearly enough.) This approach should not get in the way of any user feature, unless you'd consider "having a single, all-in-one process" to be one. It's just a design that is inherently safer, used by many security-sensitive projects, and IMO well-suited to the usual risks of running a BT client---especially when they stay up seeding for weeks/month/years at a time, and communicate with "sketchy" trackers and peers.

(Thanks for the reference to aria2, by the way. Good to know about that one, but I am looking for a BT client that can also run as a long-term seeder. Would like to keep those ratios up!)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the write up! I recall that I had read about firefox sandboxing and I was under the impression that it requires a few things: 1. Multiple processes working together. Some of the processes can drop to low privilege. 2. The ability to audit/verify that the communications between processes are within boundary. I.e. no unspecified action/command could happen.
I suppose that was the main idea.

I cannot really estimate how much work to be done in qbt. Probably it require a major rewrite of the architecture and I doubt it can be done in near future. However I would keep it in mind and let it be a long-term goal. Perhaps in the future, after enough small refactors and cleanups it will be easier to make the change.
Also FYI for @glassez @sledgehammer999.

That said, you could certainly apply the SystemCallFilter today on your own machine if you don't need the extra features.
I suppose for concerned users there could be a wiki page explaining how to harden qbt (with caveats). Our wiki is not freely to edit currently but I could help creating an entry assuming you can help providing the contents (in markdown please).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that was the main idea.

Exactly. It's basically a fail-safe(r) design approach for security, with the common refrain of, "this code is complex and it handles untrusted inputs, so let's sandbox it."

However I would keep it in mind and let it be a long-term goal.

Aye, assuredly a future-roadmap sort of thing. But if I've successfully made a case for this approach, then I'm quite happy :-)

You'd definitely want to hash out the design before any implementation work, and it's likely the design would be a bit more complex than the daemon/client duo I sketched out. For example, you wouldn't want the client piece initiating its own connections, since that ability would in itself be useful to an attacker. (E.g. connect to a home user's gateway/router, and try some default passwords...) Maybe have a separate sandboxed process initiate connections, verify that the remote end actually talks BT, and then pass the socket to the client process? That sort of thing... I'm only vaguely familiar with the BT protocol, but the key is just to think proactively/paranoically, at an abstract level. That way, you can anticipate and defend against entire classes of attacks.

As far as I'm aware, no one's yet implemented a (Linux) BT client along these lines. The other project I've been looking at is Transmission, and that goes the same all-in-one process route. The theoretical Platonic ideal would be a BT client written by the OpenBSD folks... but that's not forthcoming. Privilege separation / sandboxing would no doubt be part of their design, however, as it is for OpenSSH.

(On that note, I would suggest bookmarking the openssh-portable source code, notably the sandbox-*.c source files, as a reference point. Doesn't address Windows, but there should be other resources for that.)

That said, you could certainly apply the SystemCallFilter today on your own machine if you don't need the extra features.

I was considering that, yes, and...

I suppose for concerned users there could be a wiki page explaining how to harden qbt (with caveats). Our wiki is not freely to edit currently but I could help creating an entry assuming you can help providing the contents (in markdown please).

I think this would be a good short-to-medium-term resource for such users (on Linux, anyways). It could include not only a little-known systemd convention that enables adding config lines to a unit without needing to modify the original file, but also the use of an AppArmor profile. (The two security measures provide different kinds of protection; the syscall filter protects the kernel from oddball syscalls that the program shouldn't be using, and AppArmor restricts what file paths the program can read/write to.) Since I was planning on figuring this out anyway for my own use, making a public doc out of it wouldn't take much more effort.

Any guidelines (beyond MarkDown) to keep in mind, particular existing pages to follow the example of? How should I submit this, once it is ready?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any guidelines (beyond MarkDown) to keep in mind, particular existing pages to follow the example of?

I'm considering adding a subsection under General. It could look like this:

General

Hardening qBittorrent

AppArmor
Flatpak
systemd

Note that qbt comes with two variations. The one with GUI (qbittorrent) and the one without GUI (qbittorrent-nox). I guess the hardening flags will be different for them. It would be nice to be specific which set of flags is for which.

How should I submit this, once it is ready?

You could first write it in your repo or GIthub Gist, your choice. And provide a link in this PR. I'll copy the contents or make a link to your writings. The former is preferred as third party sites tend to rot through time but it also implies the copied content couldn't see much updates.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't be able to help much with Flatpak, as I've never used it. But the GUI program may have an AppArmor profile available, necessarily broader than the one for the daemon... I can look for that one too. (There are a few AA profiles floating around for QBT; it should be a matter of finding which is the best one, and possibly updating it.)

This will need some time to put together, simply because I have a lot on my plate. But I can promise that I won't get my own setup up and running, which will require figuring all this out, without writing up that doc. (I can't put it off anyway, or else I'll forget all the important details 😃)

@Chocobo1, thank you for your time and consideration, and you'll hear back from me again in the manner requested. Feel free to close this PR if desired; I can always post back here either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iskunk
We have a new repo for the wiki. Feel free to submit a PR over there.
https://github.com/qbittorrent/wiki

@Chocobo1 Chocobo1 added the OS: Linux Issues specific to Linux distributions label Mar 29, 2024
@luzpaz luzpaz added the Triggers Procedures activated when state changes occur (i.e. torrent completes) label Mar 31, 2024
@Chocobo1 Chocobo1 closed this May 3, 2024
@iskunk
Copy link
Author

iskunk commented May 18, 2024

(Apologies for the delay; I had been traveling recently)

Thanks for the update. I've gotten the AppArmor profile together for the daemon, and it is still pending for the GUI application.

I don't have a good place to put the systemd bit for now, so here's a copy for safekeeping:

# /etc/systemd/system/[email protected]/syscall.conf
#
# See systemd.unit(5) regarding "drop-in" directories and files
#

[Service]
SystemCallArchitectures=native
SystemCallFilter=@basic-io @file-system @io-event @network-io @process @signal @sync @system-service @timer

# end syscall.conf

This file can be placed in the indicated location, and it will be as though the content were merged into the system's [email protected] file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OS: Linux Issues specific to Linux distributions Triggers Procedures activated when state changes occur (i.e. torrent completes)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants