capabilities: Add support for Linux capabilities(7)

This adds capabilities for start-stop-daemon by adding --capabilities
option. As a result, the user can specify the inheritable, ambient and
bounding set by define capabilities in the service script.

This fixes #314.
This commit is contained in:
LinkTed 2021-06-13 19:26:24 +02:00 committed by Mike Frysinger
parent fd1e4a384a
commit 6e214b2616
14 changed files with 118 additions and 7 deletions

View File

@ -15,7 +15,9 @@ jobs:
meson \ meson \
pkgconf \ pkgconf \
linux-pam \ linux-pam \
linux-pam-dev linux-pam-dev \
libcap \
libcap-dev
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- run: meson setup builddir/ - run: meson setup builddir/

View File

@ -9,7 +9,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: sudo apt-get update -q - run: sudo apt-get update -q
- run: sudo apt-get install -q -y build-essential libpam-dev meson - run: sudo apt-get install -q -y build-essential libpam-dev meson libcap-dev
- run: meson setup builddir/ - run: meson setup builddir/
env: env:
CC: gcc CC: gcc
@ -23,7 +23,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: sudo apt-get update -q - run: sudo apt-get update -q
- run: sudo apt-get install -q -y build-essential clang libpam-dev meson - run: sudo apt-get install -q -y build-essential clang libpam-dev meson libcap-dev
- run: meson setup builddir/ - run: meson setup builddir/
env: env:
CC: clang CC: clang

View File

@ -34,6 +34,7 @@ DESTDIR=/tmp/openrc-image
MKBASHCOMP=no MKBASHCOMP=no
MKNET=no MKNET=no
MKPAM=pam MKPAM=pam
MKCAP=yes
MKPREFIX=yes MKPREFIX=yes
MKPKGCONFIG=no MKPKGCONFIG=no
MKSELINUX=yes MKSELINUX=yes

View File

@ -161,6 +161,9 @@ Cmd must be an absolute pathname, but relative to the path optionally given with
.Fl r , -chroot . .Fl r , -chroot .
This process must be prepared to accept input on stdin and be able to This process must be prepared to accept input on stdin and be able to
log it or send it to another location. log it or send it to another location.
.It Fl -capabilities Ar cap-list
Start the daemon with the listed inheritable, ambient and bounding capabilities.
The format is the same as in cap_iab(3).
.It Fl w , -wait Ar milliseconds .It Fl w , -wait Ar milliseconds
Wait Wait
.Ar milliseconds .Ar milliseconds

View File

@ -158,6 +158,9 @@ The logfile can also be a named pipe.
The same thing as The same thing as
.Fl 1 , -stdout .Fl 1 , -stdout
but with the standard error output. but with the standard error output.
.It Fl -capabilities Ar cap-list
Start the daemon with the listed inheritable, ambient and bounding capabilities.
The format is the same as in cap_iab(3).
.El .El
.Sh ENVIRONMENT .Sh ENVIRONMENT
.Va SSD_IONICELEVEL .Va SSD_IONICELEVEL

View File

@ -57,6 +57,13 @@ if not pam_dep.found() and get_option('pam')
error('Pam was requested but could not be located') error('Pam was requested but could not be located')
endif endif
cap_dep = dependency('libcap', version: '>=2.33', required : get_option('capabilities'))
if cap_dep.found()
cc_cap_flags = '-DHAVE_CAP'
else
cc_cap_flags = []
endif
option_pkg_prefix = get_option('pkg_prefix') option_pkg_prefix = get_option('pkg_prefix')
if option_pkg_prefix == '' if option_pkg_prefix == ''
if os == 'Dragonfly' or os == 'FreeBSD' if os == 'Dragonfly' or os == 'FreeBSD'

View File

@ -14,6 +14,8 @@ option('os', type : 'combo',
description : 'Operating System (autodetected if not specified)') description : 'Operating System (autodetected if not specified)')
option('pam', type : 'boolean', option('pam', type : 'boolean',
description : 'enable PAM support') description : 'enable PAM support')
option('capabilities', type : 'feature', value: 'auto',
description : 'enable capabilities support')
option('pkg_prefix', type : 'string', option('pkg_prefix', type : 'string',
description : 'default location where packages are installed') description : 'default location where packages are installed')
option('pkgconfig', type : 'boolean', option('pkgconfig', type : 'boolean',

View File

@ -27,6 +27,12 @@ endif
endif endif
ifeq (${MKCAP},yes)
CPPFLAGS+= -DHAVE_CAP
LIBCAP?= -lcap
LDADD += $(LIBCAP)
endif
ifeq (${MKAUDIT},yes) ifeq (${MKAUDIT},yes)
LIBAUDIT?= -laudit LIBAUDIT?= -laudit
CPPFLAGS+= -DHAVE_AUDIT CPPFLAGS+= -DHAVE_AUDIT

View File

@ -187,6 +187,21 @@ with
* command_user="user:group" * command_user="user:group"
If your daemon should run with specific inheritable, ambient and
bounding capabilities, then you can tell start-stop-daemon to launch
it with
* capabilities="cap-list"
The format is the same as in cap_iab(3). (Only on Linux)
For example, to start the daemon with ambient and inheritable
`cap_chown`, but without `cap_setpcap` in the bounding set, use
the following value:
```sh
capabilities="^cap_chown,!cap_setpcap"
```
Finally, if your daemon always forks into the background but fails to Finally, if your daemon always forks into the background but fails to
create a PID file, then your only option is to use create a PID file, then your only option is to use

View File

@ -53,6 +53,7 @@ ssd_start()
${error_log+--stderr} $error_log \ ${error_log+--stderr} $error_log \
${output_logger_arg} \ ${output_logger_arg} \
${error_logger_arg} \ ${error_logger_arg} \
${capabilities+--capabilities} "$capabilities" \
${procname:+--name} $procname \ ${procname:+--name} $procname \
${pidfile:+--pidfile} $pidfile \ ${pidfile:+--pidfile} $pidfile \
${command_user+--user} $command_user \ ${command_user+--user} $command_user \

View File

@ -36,6 +36,7 @@ supervise_start()
${respawn_period:+--respawn-period} $respawn_period \ ${respawn_period:+--respawn-period} $respawn_period \
${healthcheck_delay:+--healthcheck-delay} $healthcheck_delay \ ${healthcheck_delay:+--healthcheck-delay} $healthcheck_delay \
${healthcheck_timer:+--healthcheck-timer} $healthcheck_timer \ ${healthcheck_timer:+--healthcheck-timer} $healthcheck_timer \
${capabilities+--capabilities} "$capabilities" \
${command_user+--user} $command_user \ ${command_user+--user} $command_user \
${umask+--umask} $umask \ ${umask+--umask} $umask \
${supervise_daemon_args:-${start_stop_daemon_args}} \ ${supervise_daemon_args:-${start_stop_daemon_args}} \

View File

@ -93,9 +93,9 @@ executable('runscript',
executable('start-stop-daemon', executable('start-stop-daemon',
['start-stop-daemon.c', 'rc-pipes.c', rc_misc_c, rc_schedules_c, ['start-stop-daemon.c', 'rc-pipes.c', rc_misc_c, rc_schedules_c,
rc_selinux_c, usage_c, version_h], rc_selinux_c, usage_c, version_h],
c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags], c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_cap_flags, cc_selinux_flags],
link_with: [libeinfo, librc], link_with: [libeinfo, librc],
dependencies: [audit_dep, dl_dep, pam_dep, pam_misc_dep, util_dep, selinux_dep, crypt_dep], dependencies: [audit_dep, dl_dep, pam_dep, cap_dep, pam_misc_dep, util_dep, selinux_dep, crypt_dep],
include_directories: [incdir, einfo_incdir, rc_incdir], include_directories: [incdir, einfo_incdir, rc_incdir],
install: true, install: true,
install_dir: sbindir) install_dir: sbindir)
@ -103,9 +103,9 @@ executable('start-stop-daemon',
executable('supervise-daemon', executable('supervise-daemon',
['supervise-daemon.c', rc_misc_c, rc_plugin_c, rc_schedules_c, ['supervise-daemon.c', rc_misc_c, rc_plugin_c, rc_schedules_c,
usage_c, version_h], usage_c, version_h],
c_args : [cc_branding_flags, cc_pam_flags, cc_selinux_flags], c_args : [cc_branding_flags, cc_pam_flags, cc_cap_flags, cc_selinux_flags],
link_with: [libeinfo, librc], link_with: [libeinfo, librc],
dependencies: [dl_dep, pam_dep, util_dep, selinux_dep], dependencies: [dl_dep, pam_dep, cap_dep, util_dep, selinux_dep],
include_directories: [incdir, einfo_incdir, rc_incdir], include_directories: [incdir, einfo_incdir, rc_incdir],
install: true, install: true,
install_dir: sbindir) install_dir: sbindir)

View File

@ -55,6 +55,10 @@
static struct pam_conv conv = { NULL, NULL}; static struct pam_conv conv = { NULL, NULL};
#endif #endif
#ifdef HAVE_CAP
#include <sys/capability.h>
#endif
#include "einfo.h" #include "einfo.h"
#include "queue.h" #include "queue.h"
#include "rc.h" #include "rc.h"
@ -69,6 +73,7 @@ const char *extraopts = NULL;
const char getoptstring[] = "I:KN:PR:Sa:bc:d:e:g:ik:mn:op:s:tu:r:w:x:1:2:3:4:" \ const char getoptstring[] = "I:KN:PR:Sa:bc:d:e:g:ik:mn:op:s:tu:r:w:x:1:2:3:4:" \
getoptstring_COMMON; getoptstring_COMMON;
const struct option longopts[] = { const struct option longopts[] = {
{ "capabilities", 1, NULL, 0x100},
{ "ionice", 1, NULL, 'I'}, { "ionice", 1, NULL, 'I'},
{ "stop", 0, NULL, 'K'}, { "stop", 0, NULL, 'K'},
{ "nicelevel", 1, NULL, 'N'}, { "nicelevel", 1, NULL, 'N'},
@ -101,6 +106,7 @@ const struct option longopts[] = {
longopts_COMMON longopts_COMMON
}; };
const char * const longopts_help[] = { const char * const longopts_help[] = {
"Set the inheritable, ambient and bounding capabilities",
"Set an ionice class:data when starting", "Set an ionice class:data when starting",
"Stop daemon", "Stop daemon",
"Set a nicelevel when starting", "Set a nicelevel when starting",
@ -307,6 +313,9 @@ int main(int argc, char **argv)
mode_t numask = 022; mode_t numask = 022;
char **margv; char **margv;
unsigned int start_wait = 0; unsigned int start_wait = 0;
#ifdef HAVE_CAP
cap_iab_t cap_iab = NULL;
#endif
applet = basename_c(argv[0]); applet = basename_c(argv[0]);
atexit(cleanup); atexit(cleanup);
@ -353,6 +362,16 @@ int main(int argc, char **argv)
while ((opt = getopt_long(argc, argv, getoptstring, longopts, while ((opt = getopt_long(argc, argv, getoptstring, longopts,
(int *) 0)) != -1) (int *) 0)) != -1)
switch (opt) { switch (opt) {
case 0x100:
#ifdef HAVE_CAP
cap_iab = cap_iab_from_text(optarg);
if (cap_iab == NULL)
eerrorx("Could not parse iab: %s", strerror(errno));
#else
eerrorx("Capabilities support not enabled");
#endif
break;
case 'I': /* --ionice */ case 'I': /* --ionice */
if (sscanf(optarg, "%d:%d", &ionicec, &ioniced) == 0) if (sscanf(optarg, "%d:%d", &ionicec, &ioniced) == 0)
eerrorx("%s: invalid ionice `%s'", eerrorx("%s: invalid ionice `%s'",
@ -850,13 +869,29 @@ int main(int argc, char **argv)
if (changeuser && initgroups(changeuser, gid)) if (changeuser && initgroups(changeuser, gid))
eerrorx("%s: initgroups (%s, %d)", eerrorx("%s: initgroups (%s, %d)",
applet, changeuser, gid); applet, changeuser, gid);
#ifdef HAVE_CAP
if (uid && cap_setuid(uid))
#else
if (uid && setuid(uid)) if (uid && setuid(uid))
#endif
eerrorx ("%s: unable to set userid to %d", eerrorx ("%s: unable to set userid to %d",
applet, uid); applet, uid);
/* Close any fd's to the passwd database */ /* Close any fd's to the passwd database */
endpwent(); endpwent();
#ifdef HAVE_CAP
if (cap_iab != NULL) {
i = cap_iab_set_proc(cap_iab);
if (cap_free(cap_iab) != 0)
eerrorx("Could not releasable memory: %s", strerror(errno));
if (i != 0)
eerrorx("Could not set iab: %s", strerror(errno));
}
#endif
#ifdef TIOCNOTTY #ifdef TIOCNOTTY
ioctl(tty_fd, TIOCNOTTY, 0); ioctl(tty_fd, TIOCNOTTY, 0);
close(tty_fd); close(tty_fd);

View File

@ -57,6 +57,10 @@
static struct pam_conv conv = { NULL, NULL}; static struct pam_conv conv = { NULL, NULL};
#endif #endif
#ifdef HAVE_CAP
#include <sys/capability.h>
#endif
#include "einfo.h" #include "einfo.h"
#include "queue.h" #include "queue.h"
#include "rc.h" #include "rc.h"
@ -73,6 +77,7 @@ const char getoptstring[] = "A:a:D:d:e:g:H:I:Kk:m:N:p:R:r:s:Su:1:2:3" \
const struct option longopts[] = { const struct option longopts[] = {
{ "healthcheck-timer", 1, NULL, 'a'}, { "healthcheck-timer", 1, NULL, 'a'},
{ "healthcheck-delay", 1, NULL, 'A'}, { "healthcheck-delay", 1, NULL, 'A'},
{ "capabilities", 1, NULL, 0x100},
{ "respawn-delay", 1, NULL, 'D'}, { "respawn-delay", 1, NULL, 'D'},
{ "chdir", 1, NULL, 'd'}, { "chdir", 1, NULL, 'd'},
{ "env", 1, NULL, 'e'}, { "env", 1, NULL, 'e'},
@ -98,6 +103,7 @@ const struct option longopts[] = {
const char * const longopts_help[] = { const char * const longopts_help[] = {
"set an initial health check delay", "set an initial health check delay",
"set a health check timer", "set a health check timer",
"Set the inheritable, ambient and bounding capabilities",
"Set a respawn delay", "Set a respawn delay",
"Change the PWD", "Change the PWD",
"Set an environment string", "Set an environment string",
@ -152,6 +158,9 @@ static int fifo_fd = 0;
static char *pidfile = NULL; static char *pidfile = NULL;
static char *svcname = NULL; static char *svcname = NULL;
static bool verbose = false; static bool verbose = false;
#ifdef HAVE_CAP
static cap_iab_t cap_iab = NULL;
#endif
extern char **environ; extern char **environ;
@ -398,12 +407,28 @@ static void child_process(char *exec, char **argv)
eerrorx("%s: unable to set groupid to %d", applet, gid); eerrorx("%s: unable to set groupid to %d", applet, gid);
if (changeuser && initgroups(changeuser, gid)) if (changeuser && initgroups(changeuser, gid))
eerrorx("%s: initgroups (%s, %d)", applet, changeuser, gid); eerrorx("%s: initgroups (%s, %d)", applet, changeuser, gid);
#ifdef HAVE_CAP
if (uid && cap_setuid(uid))
#else
if (uid && setuid(uid)) if (uid && setuid(uid))
#endif
eerrorx ("%s: unable to set userid to %d", applet, uid); eerrorx ("%s: unable to set userid to %d", applet, uid);
/* Close any fd's to the passwd database */ /* Close any fd's to the passwd database */
endpwent(); endpwent();
#ifdef HAVE_CAP
if (cap_iab != NULL) {
i = cap_iab_set_proc(cap_iab);
if (cap_free(cap_iab) != 0)
eerrorx("Could not releasable memory: %s", strerror(errno));
if (i != 0)
eerrorx("Could not set iab: %s", strerror(errno));
}
#endif
/* remove the controlling tty */ /* remove the controlling tty */
#ifdef TIOCNOTTY #ifdef TIOCNOTTY
ioctl(tty_fd, TIOCNOTTY, 0); ioctl(tty_fd, TIOCNOTTY, 0);
@ -797,6 +822,16 @@ int main(int argc, char **argv)
eerrorx("%s: invalid health check delay %s", applet, optarg); eerrorx("%s: invalid health check delay %s", applet, optarg);
break; break;
case 0x100:
#ifdef HAVE_CAP
cap_iab = cap_iab_from_text(optarg);
if (cap_iab == NULL)
eerrorx("Could not parse iab: %s", strerror(errno));
#else
eerrorx("Capabilities support not enabled");
#endif
break;
case 'D': /* --respawn-delay time */ case 'D': /* --respawn-delay time */
n = sscanf(optarg, "%d", &respawn_delay); n = sscanf(optarg, "%d", &respawn_delay);
if (n != 1 || respawn_delay < 1) if (n != 1 || respawn_delay < 1)