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

nixos/jellyfin: add some systemd security options #98176

Merged
merged 1 commit into from Oct 20, 2020

Conversation

minijackson
Copy link
Member

Motivation for this change

I was reading through the changes introduced in transmission by #92106, and I kinda drank the Kool-Aid and thought that the Jellyfin module (among others) needed some systemd security options.

I have been using jellyfin with most of these options for a few days now, without noticing too much.

All in all, this brings the score of systemd-analyze security jellyfin from 9.2, all the way down to 1.6 🎉

To harden even more, we could:

  • use ProtectHome = true
  • use ProtectSystem = "strict"
  • use the RootDirectory and ask the end user to provide us with paths to whitelist, and use them in BindReadOnlyPaths / BindPaths

These three propositions are not in this PR, as they most likely could break existing setups, but I have been using them in my personal server, and they seem to work well:

Possible additional hardening
{
  ProtectHome = true;
  ProtectSystem = "strict";

  BindReadOnlyPaths = [
    "/nix/store"

    "/etc/ssl/certs"
    "/etc/static/ssl/certs"

    "/paths/to/allow/read-only"
  ];

  BindPaths = [ "/paths/to/allow/read-write" ];

  RuntimeDirectory = "jellyfin";
  RootDirectory = "/run/jellyfin";
}

Note: if you want to test this and you are on NixOS 20.03, note that the PrivateUsers = true; doesn't seem to work with systemd 243.

Things done
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS linux)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.

cc @nyanloutre @purcell what do you think?

@purcell
Copy link
Member

purcell commented Sep 17, 2020

Thanks for tagging me in. This seems like a Very Good Thing™ overall, and I support it, but I'm not familiar enough with the specifics to provide a useful review.

@kevincox
Copy link
Contributor

The options all look reasonable to me and if they have been working then it seems good. This may lead to a little more work to test new versions but that is probably worth it. Especially since we are using the binary release which is a bit more difficult to audit.

@purcell
Copy link
Member

purcell commented Oct 20, 2020

LGTM too

@kevincox kevincox merged commit e25cd78 into NixOS:master Oct 20, 2020
@minijackson minijackson deleted the jellyfin-systemd-security branch October 20, 2020 20:52
@d-xo
Copy link
Contributor

d-xo commented Nov 7, 2020

This commit broke playback on my iOS devices. With these options enabled, I always see lines similar to the following in the log when attempting playback from my iPad:

Nov 07 12:14:24 media jellyfin[862]: [12:14:24] [INF] [152] App: /nix/store/2fsbva8301l68byx2p5rllp4xcrdmnhq-ffmpeg-4.3.1-bin/bin/ffmpeg -fflags +genpts -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -i file:"/media/tv/some_file.mkv" -map_metadata -1 -map_chapters -1 -threads 0 -map 0:0 -map 0:1 -map -0:s -codec:v:0 copy -bsf:v h264_mp4toannexb -vsync -1 -codec:a:0 ac3 -ac 2 -ab 384000 -af "volume=2" -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time 6 -individual_header_trailer 0 -hls_segment_type mpegts -start_number 0 -hls_segment_filename "/var/lib/jellyfin/transcodes/8c910c174baf33b1da1d60100c3f6595%d.ts" -hls_playlist_type vod -hls_list_size 0 -y "/var/lib/jellyfin/transcodes/8c910c174baf33b1da1d60100c3f6595.m3u8"
Nov 07 12:14:24 media jellyfin[862]: [12:14:24] [ERR] [152] App: FFMpeg exited with code 1
Nov 07 12:14:24 media jellyfin[862]: [12:14:24] [WRN] [145] App: cannot serve /var/lib/jellyfin/transcodes/8c910c174baf33b1da1d60100c3f65950.ts as transcoding quit before we got there

after reverting this commit ffmpeg no longer crashes and playback succeeds.

@kevincox
Copy link
Contributor

kevincox commented Nov 7, 2020

Can you try running that command line and getting the ffmpeg output?

@d-xo
Copy link
Contributor

d-xo commented Nov 7, 2020

Running the command as the jellyfin user on the command line succeeds.

I don't know how to emulate the restricted execution environment of the systemd service when the security options are enabled...

@kevincox
Copy link
Contributor

kevincox commented Nov 7, 2020 via email

@minijackson
Copy link
Member Author

Shameless plug: if you need a shell inside the "sandbox" to test things out, I wrote a piece on the wiki on how to do it 🙂

@aanderse
Copy link
Member

@xwvvvvwx are you able to test this out?

@d-xo
Copy link
Contributor

d-xo commented Nov 12, 2020

Hey sorry spaced out on this. I just tested with the ffmepg command in a systemd unit with the security options enabled and I'm getting weird results. It seems to fail with the same error regardless of the security options?

The unit and error output are here: https://gist.github.com/xwvvvvwx/17c45ce3c43d64c53eeeecaea8113c6a

What's weird is that if I comment out all the security options and run systemctl daemon-reload && systemctl restart ffmpeg.service I get the same error in the logs?

@minijackson
Copy link
Member Author

From the error that you have, it seems to me that we should add char-drm to DeviceAllow.

How are you testing this exactly? Are you using a hand-placed service file? You can use systemctl show ffmpeg.service to check if your modifications are taken into account.

@d-xo
Copy link
Contributor

d-xo commented Nov 12, 2020

I am testing with a service placed in /run/systemd/system.

Adding char-drm to DeviceAllow fixed the previous error, now I have a new one:

Nov 12 12:45:16 media ffmpeg[17976]: [AVHWDeviceContext @ 0x10a8280] libva: /run/opengl-driver/lib/dri/iHD_drv_video.so has no function __vaDriverInit_1_0
Nov 12 12:45:16 media ffmpeg[17976]: [AVHWDeviceContext @ 0x10a8280] libva: /run/opengl-driver/lib/dri/i965_drv_video.so has no function __vaDriverInit_1_0
Nov 12 12:45:16 media ffmpeg[17976]: [AVHWDeviceContext @ 0x10a8280] Failed to initialise VAAPI connection: -1 (unknown libva error).
Nov 12 12:45:16 media ffmpeg[17976]: Device creation failed: -5.
Nov 12 12:45:16 media ffmpeg[17976]: Failed to set value '/dev/dri/renderD128' for option 'vaapi_device': Input/output error
Nov 12 12:45:16 media ffmpeg[17976]: Error parsing global options: Input/output error

@minijackson
Copy link
Member Author

minijackson commented Nov 12, 2020

Can you try adding char-drm rw? It is not exactly specified in the systemd.resource-control man page, but I think by default adding a device in it makes it read-only.

@d-xo
Copy link
Contributor

d-xo commented Nov 12, 2020

Same error:

Nov 12 14:02:03 media ffmpeg[8700]: [AVHWDeviceContext @ 0x4d1280] libva: /run/opengl-driver/lib/dri/iHD_drv_video.so has no function __vaDriverInit_1_0
Nov 12 14:02:03 media ffmpeg[8700]: [AVHWDeviceContext @ 0x4d1280] libva: /run/opengl-driver/lib/dri/i965_drv_video.so has no function __vaDriverInit_1_0
Nov 12 14:02:03 media ffmpeg[8700]: [AVHWDeviceContext @ 0x4d1280] Failed to initialise VAAPI connection: -1 (unknown libva error).
Nov 12 14:02:03 media ffmpeg[8700]: Device creation failed: -5.
Nov 12 14:02:03 media ffmpeg[8700]: Failed to set value '/dev/dri/renderD128' for option 'vaapi_device': Input/output error
Nov 12 14:02:03 media ffmpeg[8700]: Error parsing global options: Input/output error

@minijackson
Copy link
Member Author

Pure speculation here, but looking at the Device creation failed: -5. we might need the rwm permissions? If it is not that, we might need to do some more digging to see what kind of device ffmpeg tries to create

@minijackson
Copy link
Member Author

minijackson commented Nov 12, 2020

So, I've had some time to try and reproduce the error. Here is the script I have to try to get in your situation:

Script
#!/bin/sh
systemd-run \
	--pty \
	\
	-p BindReadOnlyPaths=/home/minijackson/Videos \
	-p ProtectHome=tmpfs \
	\
	-p NoNewPrivileges=true \
	-p DeviceAllow="char-drm rw" \
	-p AmbientCapabilities= \
	-p CapabilityBoundingSet= \
	-p LockPersonality=true \
	\
	-p PrivateTmp=true \
	-p PrivateUsers=true \
	\
	-p ProtectClock=true \
	-p ProtectControlGroups=true \
	-p ProtectHostname=true \
	-p ProtectKernelLogs=true \
	-p ProtectKernelModules=true \
	-p ProtectKernelTunables=true \
	\
	-p RemoveIPC=true \
	\
	-p RestrictNamespaces=true \
	-p RestrictAddressFamilies="AF_NETLINK AF_INET AF_INET6" \
	-p RestrictRealtime=true \
	-p RestrictSUIDSGID=true \
	\
	-p SystemCallArchitectures=native \
	-p SystemCallErrorNumber=EPERM \
	-p SystemCallFilter="@system-service" \
	-p SystemCallFilter="~@chown" \
	-p SystemCallFilter="~@cpu-emulation" \
	-p SystemCallFilter="~@debug" \
	-p SystemCallFilter="~@keyring" \
	-p SystemCallFilter="~@memlock" \
	-p SystemCallFilter="~@module" \
	-p SystemCallFilter="~@obsolete" \
	-p SystemCallFilter="~@privileged" \
	-p SystemCallFilter="~@setuid" \
	ffmpeg \
		-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 \
		-i file:/home/minijackson/Videos/$RANDOM_VIDEO_FILE_I_HAVE_ON_MY_COMPUTER.mkv \
		-map_metadata -1 -map_chapters -1 \
		-threads 0 -map 0:0 -map 0:1 -codec:v:0 h264_vaapi \
		-b:v 639378 -maxrate 639378 -bufsize 1278756 -profile:v high -level 41  -force_key_frames:0 "expr:gte(t,0+n_forced*3)" -g 72 -keyint_min 72 -sc_threshold 0 \
		-vf "format=nv12|vaapi,hwupload,scale_vaapi=w=638:h=266:format=nv12" \
		-start_at_zero -vsync -1 -codec:a:0 libmp3lame -ac 2 -ab 160622  -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time 3 -individual_header_trailer 0 -hls_segment_type mpegts -start_number 0 -hls_playlist_type vod -hls_list_size 0 -y \
		/tmp/thing.m3u8

Some things to note:

  • I see that you have added ProtectDevices=true in your unit file, but the option is actually called PrivateDevices 😉 and it actually makes a new /dev mount without the drm device, so it cannot work with this option. That's another thing to fix already!
  • SystemCallFilter doesn't seem to be parsed by systemd when specified on the same line, that's weird because it works fine thi RestrictAddressFamilies
  • It actually works on my machine! I have tried it also with DynamicUser=true just in case specifying a different user would change something, and it also worked :-/

@xwvvvvwx Can you tell me if this script is also working for you?

I have missed it in my previous message, but it seems your last error comes from a missing symbol, can you check if you have access to the libraries from inside the unit, and if they do contain the symbol ffmpeg is looking for?

minijackson added a commit to minijackson/nixpkgs that referenced this pull request Jan 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants