fix(systemd): Add back chown allowed syscalls
IFF the user enables the `syncOwnership` feature AND sets
`AmbientCapabilities=CAP_CHOWN CAP_FOWNER` as the docs in
https://docs.syncthing.net/users/autostart.html#permissions state,
THEN syncthing needs to use the `chown` syscall.
PR #10421 added a comprehensive sandbox that breaks `syncOwnership`.
In PR #10602 we fixed one part, which is expanding the default
`CapabilityBoundingSet` (see the PR for details).
But there's a very subtle bug that this PR fixes. PR #10421 sets the
following properties:
SystemCallFilter=@system-service
SystemCallFilter=~@privileged io_uring_enter io_uring_register io_uring_setup
(Systemd merges `SystemCallFilter` values; we had to set the property
twice because to negate syscalls, the whole list has to start with `~`.)
The goal was to allow all syscalls in the `@system-service` set, BUT
disallow any `@privileged` syscalls and the `io_uring*` syscalls.
But the sets are not disjoint; `chown` is in both `@system-service` and
in `@privileged`, so it is removed from the allow list by the second
property value.
This property is also parsed in a very peculiar way. From systemd docs:
> If you specify both types of this option (i.e. allow-listing and
> deny-listing), the first encountered will take precedence and will
> dictate the default action (termination or approval of a system call).
> Then the next occurrences of this option will add or delete the listed
> system calls from the set of the filtered system calls, depending of its
> type and the default action. (For example, if you have started with an
> allow list rule for read() and write(), and right after it add a deny
> list rule for write(), then write() will be removed from the set.)
Not only does the order of `SystemCallFilter` properties matter (later
ones can undo effects of prior ones), but the _type_ of the _first_
property sets the overall behavior of the syscall filter: if the first
`SystemCallFilter` value is an allow list, then all syscalls that are
not specified are disallowed by default (and reverse if the first value
is a deny list).
Of course, this is completely different from how other allow/deny lists
are implemented in systemd; for example, `IPAddress[Allow|Deny]`
properties don't work like this at all. >:(
Since this complexity has bit us once, we're removing the additional
deny list of syscalls and sticking with just
`SystemCallFilter=@system-service`.
This leaves some privileged syscalls in the allow list. Other options
would require entering the "deny list by default" mode and deny lists
are less secure than allow lists in general because they have to be
maintained (the kernel always adds new syscalls). The rest of the
sandbox (capability bounds) should be sufficient.
Fixes#10603
Signed-off-by: Val Markovic <val@markovic.io>
Syncthing docs in https://docs.syncthing.net/users/autostart.html#permissions
tell the user to set `AmbientCapabilities=CAP_CHOWN CAP_FOWNER` if the
user wishes to use the `syncOwnership` option.
https://github.com/syncthing/syncthing/pull/10421 broke `syncOwnership`
for users that followed that advice because the PR introduced
`CapabilityBoundingSet=` which cancels out any additional capabilities
granted with `AmbientCapabilities`.
(`AmbientCapabilities` _adds_ capabilities; `CapabilityBoundingSet`
_limits_ maximum capabilities to the specified set. Setting
`CapabilityBoundingSet` to an empty list prevents any capabilities from
being acquired in any way.)
This PR fixes the breakage by explicitly setting
CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER
This does _not_ grant any additional access rights to syncthing if the
user is not explicitly setting `AmbientCapabilities` as well, but it
does loosen the sandbox _a bit_. An attacker compromising the syncthing
process could now more easily expand their access to include
CAP_CHOWN/CAP_FOWNER even if the user is not setting
`AmbientCapabilities`.
Signed-off-by: Val Markovic <val@markovic.io>
Update the existing minimal service hardening with a comprehensive
sandbox to minimize blast damage from service compromise.
Please see the detailed code comments for an explanation of what is
sandboxed.
Roughly, we limit: /dev, /proc, /tmp, AF_UNIX, AF_PACKET, execution of
_any_ binary other than "/usr/bin/syncthing" and "/usr/lib",
uncommon syscalls plus io_uring, tons of kernel internals and more. We
also enable a bunch of kernel namespaces for isolation.
In short, pretty much everything is sandboxed and specifically tuned for
syncthing's behavior.
Sadly, we cannot use ProtectSystem=strict by default because we don't
know the directories that the user will be sharing. There's a big
comment block explaining how users can enable it for "extra credit". :)
If the user did add the following options as the unit file recommends:
- ProtectSystem=strict
- ReadWritePaths=/my/shared/dir1 /my/shared/dir2
- ProtectHome=true
Then the user would end up with a *far* more comprehensive sandbox than
anything a container runtime (like Docker/Podman/whatever) would
provide.
Much (but not all) of these options could be ported to the
user/syncthing.service file, BUT it would require work. Systemd does not
allow all of these options to be used with the user service manager,
although using PrivateUsers=true would help with most of it.
I cannot justify the time investment to develop, audit and test the
port to user/syncthing.service so I leave that for interested
contributors.
Tested on Debian Trixie (13) with the following versions:
- v1.29.5, Linux (64-bit Intel/AMD)
- latest HEAD (d3d3fc2d0 committed on Mon Oct 6 01:42:58 2025)
Signed-off-by: Val Markovic <val@markovic.io>
This adds several options for configuring the log format of timestamps
and severity levels, making it more suitable for integration with log
systems like systemd.
--log-format-timestamp="2006-01-02 15:04:05"
Format for timestamp, set to empty to disable timestamps ($STLOGFORMATTIMESTAMP)
--[no-]log-format-level-string
Whether to include level string in log line ($STLOGFORMATLEVELSTRING)
--[no-]log-format-level-syslog
Whether to include level as syslog prefix in log line ($STLOGFORMATLEVELSYSLOG)
So, to get a timestamp suitable for systemd (syslog prefix, no level
string, no timestamp) we can pass `--log-format-timestamp=""
--no-log-format-level-string --log-format-level-syslog` or,
equivalently, set `STLOGFORMATTIMESTAMP="" STLOGFORMATLEVELSTRING=false
STLOGFORMATLEVELSYSLOG=true`.
Signed-off-by: Jakob Borg <jakob@kastelo.net>
This updates our logging framework from legacy freetext strings using
the `log` package to structured log entries using `log/slog`. I have
updated all INFO or higher level entries, but not yet DEBUG (😓)... So,
at a high level:
There is a slight change in log levels, effectively adding a new warning
level:
- DEBUG is still debug (ideally not for users but developers, though
this is something we need to work on)
- INFO is still info, though I've added more data here, effectively
making Syncthing more verbose by default (more on this below)
- WARNING is a new log level that is different from the _old_ WARNING
(more below)
- ERROR is what was WARNING before -- problems that must be dealt with,
and also bubbled as a popup in the GUI.
A new feature is that the logging level can be set per package to
something other than just debug or info, and hence I feel that we can
add a bit more things into INFO while moving some (in fact, most)
current INFO level warnings into WARNING. For example, I think it's
justified to get a log of synced files in INFO and sync failures in
WARNING. These are things that have historically been tricky to debug
properly, and having more information by default will be useful to many,
while still making it possible get close to told level of inscrutability
by setting the log level to WARNING. I'd like to get to a stage where
DEBUG is never necessary to just figure out what's going on, as opposed
to trying to narrow down a likely bug.
Code wise:
- Our logging object, generally known as `l` in each package, is now a
new adapter object that provides the old API on top of the newer one.
(This should go away once all old log entries are migrated.) This is
only for `l.Debugln` and `l.Debugf`.
- There is a new level tracker that keeps the log level for each
package.
- There is a nested setup of handlers, since the structure mandated by
`log/slog` is slightly convoluted (imho). We do this because we need to
do formatting at a "medium" level internally so we can buffer log lines
in text format but with separate timestamp and log level for the API/GUI
to consume.
- The `debug` API call becomes a `loglevels` API call, which can set the
log level to `DEBUG`, `INFO`, `WARNING` or `ERROR` per package. The GUI
is updated to handle this.
- Our custom `sync` package provided some debugging of mutexes quite
strongly integrated into the old logging framework, only turned on when
`STTRACE` was set to certain values at startup, etc. It's been a long
time since this has been useful; I removed it.
- The `STTRACE` env var remains and can be used the same way as before,
while additionally permitting specific log levels to be specified,
`STTRACE=model:WARN,scanner:DEBUG`.
- There is a new command line option `--log-level=INFO` to set the
default log level.
- The command line options `--log-flags` and `--verbose` go away, but
are currently retained as hidden & ignored options since we set them by
default in some of our startup examples and Syncthing would otherwise
fail to start.
Sample format messages:
```
2009-02-13 23:31:30 INF A basic info line (attr1="val with spaces" attr2=2 attr3="val\"quote" a=a log.pkg=slogutil)
2009-02-13 23:31:30 INF An info line with grouped values (attr1=val1 foo.attr2=2 foo.bar.attr3=3 a=a log.pkg=slogutil)
2009-02-13 23:31:30 INF An info line with grouped values via logger (foo.attr1=val1 foo.attr2=2 a=a log.pkg=slogutil)
2009-02-13 23:31:30 INF An info line with nested grouped values via logger (bar.foo.attr1=val1 bar.foo.attr2=2 a=a log.pkg=slogutil)
2009-02-13 23:31:30 WRN A warning entry (a=a log.pkg=slogutil)
2009-02-13 23:31:30 ERR An error (a=a log.pkg=slogutil)
```
---------
Co-authored-by: Ross Smith II <ross@smithii.com>
Add a commented entry to the systemd service file templates to point
the user in the right direction when using syncOwnership and starting
via systemd. Which is more upgrade-friendly than setting caps on the
executable directly, as mentioned in the docs.
The ```syncthing-resume.service``` will show as a failed service in case
there are no syncthing processes running after resume but it can be
safely ignored because it makes no difference.
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3630
Before this change, issuing either
systemctl --user help syncthing[.service]
or
systemctl help syncthing@user[.service]
gave the message
Can't show: http://docs.syncthing.net/
Following this change the syncthing man page is displayed
This systemd service restarts Syncthing after resume from suspend
via sending SIGHUP. By default Syncthing detects resume from sleep
on its own by looking for jumps in the system clock. Since systemd
knows exactly when the system resumes from sleep let's trigger
the Syncthing restart from there. Doing this in systemd eliminates
some annoying delay, as the service is restarted immediately after
resume. Also, using the systemd dependency mechanism syncthing-inotify
is restarted as well.
$ journalctl -e --identifier syncthing --identifier syncthing-inotify --identifier systemd
Feb 22 09:44:27 kronos systemd[1]: Reached target Sleep.
Feb 22 09:44:27 kronos systemd[1]: Starting Suspend...
Feb 22 09:44:33 kronos systemd[1]: Time has been changed
Feb 22 09:44:33 kronos systemd[963]: Time has been changed
Feb 22 09:44:33 kronos systemd[1]: Started Suspend.
Feb 22 09:44:33 kronos systemd[1]: sleep.target: Unit not needed anymore. Stopping.
Feb 22 09:44:33 kronos systemd[1]: Stopped target Sleep.
Feb 22 09:44:33 kronos systemd[1]: Reached target Suspend.
Feb 22 09:44:33 kronos systemd[1]: suspend.target: Unit is bound to inactive unit systemd-suspend.service. Stopping, too.
Feb 22 09:44:33 kronos systemd[1]: Stopped target Suspend.
Feb 22 09:44:33 kronos systemd[1]: Starting Restart Syncthing after resume...
Feb 22 09:44:33 kronos syncthing[2561]: [35K66] OK: Exiting
Feb 22 09:44:33 kronos systemd[1]: Started Restart Syncthing after resume.
Feb 22 09:44:34 kronos systemd[963]: syncthing.service: Service hold-off time over, scheduling restart.
Feb 22 09:44:34 kronos systemd[963]: Stopping Syncthing Inotify File Watcher...
Feb 22 09:44:34 kronos systemd[963]: Stopped Syncthing Inotify File Watcher.
Feb 22 09:44:34 kronos systemd[963]: Stopped Syncthing - Open Source Continuous File Synchronization.
Feb 22 09:44:34 kronos systemd[963]: Started Syncthing - Open Source Continuous File Synchronization.
Feb 22 09:44:34 kronos systemd[963]: Started Syncthing Inotify File Watcher.
Feb 22 09:44:34 kronos syncthing[2836]: [35K66] INFO: syncthing v0.12.19 "Beryllium Bedbug" (go1.5.3 linux-amd64) builduser@svetlemodry 2016-02-14 19:26:33 UTC
This system service has to be located in "/etc/systemd/system/syncthing-resume.service",
and for packages in "/usr/lib/systemd/system/syncthing-resume.service". It can be
enabled using "systemctl enable syncthing-resume.service".
PR #2578 enables us to remove the exit code 2 from the list of
success status codes, because SIGINT will be handled properly.
I have also converted STNORESTART to --no-restart for the sake
of consistency.
The systemd exit code definitions (introduced in c586a17, refs #1324) caused
problems. Per default systemd considers a return code != 0 as failed. So when
you click on "Restart" in the WebUI an error appears:
systemd[953]: syncthing.service: main process exited, code=exited, status=3/NOTIMPLEMENTED
systemd[953]: Unit syncthing.service entered failed state.
systemd[953]: syncthing.service failed.
systemd[953]: syncthing.service holdoff time over, scheduling restart.
systemd[953]: Started Syncthing - Open Source Continuous File Synchronization.
systemd[953]: Starting Syncthing - Open Source Continuous File Synchronization...
syncthing[13222]: [LFKUK] INFO: syncthing v0.10.30 (go1.4.2 linux-amd64 default) builduser@jara 2015-03-29 07:46:44 UTC
To fix this error we have to add the "succes codes" 2, 3, 4 to
"SuccessExitStatus":
syncthing[13006]: [LFKUK] INFO: Restarting
syncthing[13006]: [LFKUK] OK: Exiting
systemd[953]: syncthing.service holdoff time over, scheduling restart.
systemd[953]: Started Syncthing - Open Source Continuous File Synchronization.
systemd[953]: Starting Syncthing - Open Source Continuous File Synchronization...
syncthing[13031]: [LFKUK] INFO: syncthing v0.10.30 (go1.4.2 linux-amd64 default) builduser@jara 2015-03-29 07:46:44 UTC
To make sure that syncthing restarts in case of error, and to make sure
that "Restart=on-failure" actually works, let's remove
"RestartPreventExitStatus=1". Systemd considers this as an error per
default and the restart will be triggered successfully.
Syncthing should not try to start a browser when invoked by systemd.
Furthermore we do not need any timestamps in the journal as systemd
already handles this for us.
- Removed environment file to keep the service file minimal.
"systemctl edit syncthing.service" does the job if somebody wants
to customize the service.
- Changed "cmdline.target" to "default.target" as "cmdline.target"
does not exist in systemd.special:
http://www.freedesktop.org/software/systemd/man/systemd.special.html
- Added a missing "After=network.target".
- Added a documentation hint, thx @jaystrictor