From: Till Kamppeter Date: Tue, 2 Mar 2021 19:29:41 +0000 (+0100) Subject: scheduler: Added remaining changes needed to run CUPS as a Snap X-Git-Tag: v2.4b1~249 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=40088fca0dfbb3472bdc217f2d3d1fd84f731630;p=thirdparty%2Fcups.git scheduler: Added remaining changes needed to run CUPS as a Snap cupsd has a lot of functionality, especially for security, which does not work under the confinement of a Snap. So these features need to get removed or modified when CUPS is intended to get snapped, but removing them does not necessarily weaken the security of the whole thing, as the confinement of the Snap adds security replacing the one removed from CUPS itself. Most importantly a Snap cannot create several system groups and users. Instead, it has a single unprivileged system user and a single unprivileged system group, both named "snap_daemon". These are replacing "lp" for running print jobs and filters, for administration the user is root and as group first "lpadmin" and then "adm" is tried and used if the host system has such a group. In all cases "root" is also an admin group. In addition, fiie ACLs do not work inside a Snap and the PATH and LD_LIBRARY_PATH environment variables of the Snap are passed on to the executables started by CUPS, so that they find their files and libraries on the Snap-specific places. Both the scheduler and the debugging tool cupsfilter are appropriately modified. This commit contains all changes to make the scheduler work under these conditions, but to no compromise the security of an unsnapped build of CUPS, conditional compiling is used. The changes are only applied if the "--enable-snapped-cupsd" ./configure option is used. Also the checking whether a client Snap doing administrative tasks plugs "cups-control" (my previous commit) is then active. "--enable-snapped-clients" is still available to do only the client checking with an unsnapped scheduler. All these changes got tested as packaging patches inn both the CUPS Snap and the Debian/Ubuntu package of CUPS. Note that this commit is not containing the changes on the (machine-generated) ./configure script and so will not pass the CI tests. An additional commit with an autoconf rebuild of ./configure is needed. --- diff --git a/CHANGES.md b/CHANGES.md index 3f9c74fc76..05cb55c8c2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ CUPS v2.4rc1 (Pending) ---------------------- - Added support for CUPS running in a snapcraft snap. +- Added extra check for administrative inquiries from snapped clients. - The `testlang` unit test program now loops over all of the available locales by default (Issue #85) - The `cupsfilter` command now shows error messages when options are used diff --git a/config-scripts/cups-snap.m4 b/config-scripts/cups-snap.m4 index a16446f1a5..206850011a 100644 --- a/config-scripts/cups-snap.m4 +++ b/config-scripts/cups-snap.m4 @@ -1,14 +1,14 @@ dnl -dnl Support for packaging CUPS in a Snap. +dnl Support for packaging CUPS in a Snap and have it work with client Snaps. dnl -dnl Copyright © 2020 by Till Kamppeter -dnl Copyright © 2007-2019 by Apple Inc. +dnl Copyright © 2021 by Till Kamppeter +dnl Copyright © 2021 by OpenPrinting dnl dnl Licensed under Apache License v2.0. See the file "LICENSE" for more dnl information. dnl -# Snap packaging support +# Snap packaging and Snap interaction support AC_ARG_ENABLE(snapped_cupsd, [ --enable-snapped-cupsd enable support for packaging CUPS in a Snap]) AC_ARG_ENABLE(snapped_clients, [ --enable-snapped-clients enable support for CUPS controlling admin access from snapped clients]) @@ -47,7 +47,8 @@ if test "x$PKGCONFIG" != x -a x$enable_snapped_clients == xyes; then if test "x$SNAPCTL" != x && $SNAPCTL is-connected --help >/dev/null 2>&1; then AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SNAPCTL_IS_CONNECTED) - AC_DEFINE(BUILD_SNAP) + AC_DEFINE(SUPPORT_SNAPPED_CUPSD) + AC_DEFINE(SUPPORT_SNAPPED_CLIENTS) ENABLE_SNAPPED_CUPSD="YES" ENABLE_SNAPPED_CLIENTS="YES" else @@ -60,7 +61,7 @@ if test "x$PKGCONFIG" != x -a x$enable_snapped_clients == xyes; then CFLAGS="$CFLAGS `$PKGCONFIG --cflags snapd-glib glib-2.0 gio-2.0`" SNAPDGLIBLIBS="`$PKGCONFIG --libs snapd-glib glib-2.0 gio-2.0`" AC_DEFINE(HAVE_SNAPDGLIB) - AC_DEFINE(BUILD_SNAP) + AC_DEFINE(SUPPORT_SNAPPED_CLIENTS) ENABLE_SNAPPED_CLIENTS="YES" else AC_MSG_RESULT(no) @@ -73,7 +74,11 @@ fi AC_MSG_CHECKING(for Snap support) if test "x$ENABLE_SNAPPED_CLIENTS" != "xNO"; then - AC_MSG_RESULT(yes) + if test "x$ENABLE_SNAPPED_CUPSD" != "xNO"; then + AC_MSG_RESULT(yes: cupsd + clients) + else + AC_MSG_RESULT(yes: clients only) + fi else AC_MSG_RESULT(no) fi diff --git a/config.h.in b/config.h.in index 47b826c8bc..7710edf3c2 100644 --- a/config.h.in +++ b/config.h.in @@ -671,7 +671,8 @@ #undef HAVE_SNAPCTL_IS_CONNECTED #undef SNAPCTL #undef CUPS_CONTROL_SLOT -#undef BUILD_SNAP +#undef SUPPORT_SNAPPED_CUPSD +#undef SUPPORT_SNAPPED_CLIENTS /* * Location of macOS localization bundle, if any. diff --git a/scheduler/auth.c b/scheduler/auth.c index 0107e15cc3..6307e81d3f 100644 --- a/scheduler/auth.c +++ b/scheduler/auth.c @@ -51,13 +51,15 @@ typedef struct sockpeercred cupsd_ucred_t; # endif # define CUPSD_UCRED_UID(c) (c).uid #endif /* HAVE_SYS_UCRED_H */ -#ifdef BUILD_SNAP -# include +#ifdef SUPPORT_SNAPPED_CLIENTS +# ifdef HAVE_APPARMOR +# include +# endif # ifdef HAVE_SNAPDGLIB # include # include # endif -#endif /* BUILD_SNAP */ +#endif /* SUPPORT_SNAPPED_CLIENTS */ /* @@ -1546,7 +1548,7 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ * occurs in the process of finding this out. */ -#if defined(AF_LOCAL) && defined(BUILD_SNAP) +#if defined(AF_LOCAL) && defined(SUPPORT_SNAPPED_CLIENTS) /* * Get the client's file descriptor and from this its AppArmor context @@ -1565,7 +1567,7 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ else { char *context = NULL; /* AppArmor profile name of client */ -# ifdef HAVE_SNAPCTL_IS_CONNECTED +# if defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPCTL_IS_CONNECTED) char buf[1024]; char *argv[5]; /* snapctl command line */ int fds[2], /* Pipe file descriptors for stderr of snapctl */ @@ -1575,20 +1577,21 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ int status = 65536; /* Status of forked snapctl process */ int wstatus; /* Wait result of forked snapctl process */ # else -# ifdef HAVE_SNAPDGLIB +# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) char *snap_name = NULL; /* Client Snap name */ char *dot; /* Pointer to dot in AppArmor profile name */ SnapdClient *snapd = NULL; /* Data structure of snapd access */ SnapdSnap *snap = NULL; /* Data structure of client Snap */ GPtrArray *plugs = NULL; /* Plug search result of client Snap */ GError *error = NULL; /* Glib error */ -# endif /* HAVE_SNAPDGLIB */ -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ +# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ +# endif /* SUPPORT_SNAPPED_CUPSD && HAVE_SNAPCTL_IS_CONNECTED */ + -# ifndef HAVE_SNAPCTL_IS_CONNECTED +# ifndef SUPPORT_SNAPPED_CUPSD /* If AppArmor is not enabled, then we can't identify the client */ - /* With cupsd running in a Snaps, the "mount-observe" interface + /* With cupsd running in a Snap, the "mount-observe" interface needs to be plugged, therefore we do this only if not snapped. */ if (!aa_is_enabled()) { @@ -1596,7 +1599,7 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ goto snap_check_done; } -# endif /* !HAVE_SNAPCTL_IS_CONNECTED */ +# endif /* !SUPPORT_SNAPPED_CUPSD */ if (aa_getpeercon(peerfd, &context, NULL) < 0) { @@ -1605,7 +1608,7 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ } else cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: AppArmor profile of client process: %s", context); -# ifdef HAVE_SNAPCTL_IS_CONNECTED +# if defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPCTL_IS_CONNECTED) /* * Run @@ -1789,13 +1792,13 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ free(context); # else -# ifdef HAVE_SNAPDGLIB +# if !defined(SUPPORT_SNAPPED_CUPSD) && defined(HAVE_SNAPDGLIB) /* * If the client is a Snap, extract the client Snap's name from - * the AppArmor context and the query snapd to find out about the - * Snap's confinement type and whether it plugs - * cups-control. Grant access if + * the AppArmor context and then query snapd to find out about the + * Snap's confinement type and whether it plugs cups-control. Grant + * access if * * - the client is not a Snap * - the client is a classic Snap @@ -1814,8 +1817,8 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ * Snap Store. This is the reason why the snapctl method * (above) got created by the snapd developers. * - * This is the preferred method to run CUPS unsnapped, as is the - * only way to check Snap status on clients from an unsnapped + * This is the preferred method to run CUPS unsnapped, as this is + * the only way to check Snap status on clients from an unsnapped * cupsd. */ @@ -1903,16 +1906,20 @@ cupsdCheckAdminTask(cupsd_client_t *con) /* I - Connection */ if (context) free(context); -# endif /* HAVE_SNAPDGLIB */ -# endif /* HAVE_SNAPCTL_IS_CONNECTED */ +# endif /* !SUPPORT_SNAPPED_CUPSD && HAVE_SNAPDGLIB */ +# endif /* SUPPORT_SNAPPED_CUPSD && HAVE_SNAPCTL_IS_CONNECTED */ } } -#endif /* AF_LOCAL && BUILD_SNAP */ - cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access %s", ret == 1 ? "granted" : "denied"); +#else + + cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCheckAdminTask: Access granted (no extra checking)"); + +#endif /* AF_LOCAL && SUPPORT_SNAPPED_CLIENTS */ + return ret; } diff --git a/scheduler/cert.c b/scheduler/cert.c index 258e8fc833..9716bbf355 100644 --- a/scheduler/cert.c +++ b/scheduler/cert.c @@ -83,7 +83,7 @@ cupsdAddCert(int pid, /* I - Process ID */ if (pid == 0) { -#ifdef HAVE_ACL_INIT +#if defined(HAVE_ACL_INIT) && !defined(SUPPORT_SNAPPED_CUPSD) acl_t acl; /* ACL information */ acl_entry_t entry; /* ACL entry */ acl_permset_t permset; /* Permissions */ @@ -92,7 +92,7 @@ cupsdAddCert(int pid, /* I - Process ID */ # endif /* HAVE_MBR_UID_TO_UUID */ static int acls_not_supported = 0; /* Only warn once */ -#endif /* HAVE_ACL_INIT */ +#endif /* HAVE_ACL_INIT && !SUPPORT_SNAPPED_CUPSD */ /* @@ -100,11 +100,18 @@ cupsdAddCert(int pid, /* I - Process ID */ */ fchmod(fd, 0440); + + /* ACLs do not work when cupsd is running in a Snap, and certificates + need root as group owner to be only accessible for CUPS and not the + unprivileged sub-processes */ +#ifdef SUPPORT_SNAPPED_CUPSD + fchown(fd, RunUser, 0); +#else fchown(fd, RunUser, SystemGroupIDs[0]); cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddCert: NumSystemGroups=%d", NumSystemGroups); -#ifdef HAVE_ACL_INIT +# ifdef HAVE_ACL_INIT if (NumSystemGroups > 1) { /* @@ -114,7 +121,7 @@ cupsdAddCert(int pid, /* I - Process ID */ int j; /* Looping var */ -# ifdef HAVE_MBR_UID_TO_UUID +# ifdef HAVE_MBR_UID_TO_UUID /* * On macOS, ACLs use UUIDs instead of GIDs... */ @@ -143,7 +150,7 @@ cupsdAddCert(int pid, /* I - Process ID */ acl_set_permset(entry, permset); } -# else +# else /* * POSIX ACLs need permissions for owner, group, other, and mask * in addition to the rest of the system groups... @@ -215,7 +222,7 @@ cupsdAddCert(int pid, /* I - Process ID */ cupsdLogMessage(CUPSD_LOG_ERROR, "ACL: %s", text); acl_free(text); } -# endif /* HAVE_MBR_UID_TO_UUID */ +# endif /* HAVE_MBR_UID_TO_UUID */ if (acl_set_fd(fd, acl)) { @@ -230,7 +237,8 @@ cupsdAddCert(int pid, /* I - Process ID */ acl_free(acl); } -#endif /* HAVE_ACL_INIT */ +# endif /* HAVE_ACL_INIT */ +#endif /* SUPPORT_SNAPPED_CUPSD */ RootCertTime = time(NULL); } diff --git a/scheduler/conf.c b/scheduler/conf.c index 74531a8c78..8944f69273 100644 --- a/scheduler/conf.c +++ b/scheduler/conf.c @@ -1132,8 +1132,16 @@ cupsdReadConfiguration(void) Group, 1, 1) < 0 || cupsdCheckPermissions(StateDir, NULL, 0755, RunUser, Group, 1, 1) < 0 || + /* Inside a Snap cupsd is running as root without CAP_DAC_OVERRIDE + capability, so certs directory has to be root.root-owned so that + cupsd can access but not its unprivileged sub-processes. */ +#ifdef SUPPORT_SNAPPED_CUPSD + cupsdCheckPermissions(StateDir, "certs", 0711, RunUser, + 0, 1, 1) < 0 || +#else cupsdCheckPermissions(StateDir, "certs", RunUser ? 0711 : 0511, User, SystemGroupIDs[0], 1, 1) < 0 || +#endif /* SUPPORT_SNAPPED_CUPSD */ cupsdCheckPermissions(ServerRoot, NULL, 0755, RunUser, Group, 1, 0) < 0 || cupsdCheckPermissions(ServerRoot, "ppd", 0755, RunUser, diff --git a/scheduler/cupsfilter.c b/scheduler/cupsfilter.c index 54f6ba5b85..9772242c9b 100644 --- a/scheduler/cupsfilter.c +++ b/scheduler/cupsfilter.c @@ -970,7 +970,11 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ { int i; /* Looping var */ const char *argv[8], /* Command-line arguments */ +#ifdef SUPPORT_SNAPPED_CUPSD + *envp[21], /* Environment variables */ +#else *envp[17], /* Environment variables */ +#endif /* SUPPORT_SNAPPED_CUPSD */ *temp; /* Temporary string */ char *optstr, /* Filter options */ content_type[1024], /* CONTENT_TYPE */ @@ -978,9 +982,17 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ cups_fontpath[1024], /* CUPS_FONTPATH */ cups_serverbin[1024], /* CUPS_SERVERBIN */ cups_serverroot[1024], /* CUPS_SERVERROOT */ +#ifdef SUPPORT_SNAPPED_CUPSD + fontconfig_file[1024], /* FONTCONFIG_FILE */ + fontconfig_path[1024], /* FONTCONFIG_PATH */ + fontconfig_sysroot[1024], /* FONTCONFIG_SYSROOT */ +#endif /* SUPPORT_SNAPPED_CUPSD */ final_content_type[1024] = "", /* FINAL_CONTENT_TYPE */ lang[1024], /* LANG */ +#ifdef SUPPORT_SNAPPED_CUPSD + ld_library_path[2048], /* LD_LIBRARY_PATH */ +#endif /* SUPPORT_SNAPPED_CUPSD */ path[1024], /* PATH */ ppd[1024], /* PPD */ printer_info[255], /* PRINTER_INFO env variable */ @@ -1039,6 +1051,9 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ * Setup the filter environment and command-line... */ + /* If we are running confined in a Snap, also pass on fontconfig-related + environment variables and LD_LIBRARY_PATH */ + optstr = escape_options(num_options, options); snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s", @@ -1049,8 +1064,20 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ ServerBin); snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s", ServerRoot); +#ifdef SUPPORT_SNAPPED_CUPSD + snprintf(fontconfig_file, sizeof(fontconfig_file), "FONTCONFIG_FILE=%s", + getenv("FONTCONFIG_FILE")); + snprintf(fontconfig_path, sizeof(fontconfig_path), "FONTCONFIG_PATH=%s", + getenv("FONTCONFIG_PATH")); + snprintf(fontconfig_sysroot, sizeof(fontconfig_sysroot), + "FONTCONFIG_SYSROOT=%s", getenv("FONTCONFIG_SYSROOT")); +#endif /* SUPPORT_SNAPPED_CUPSD */ language = cupsLangDefault(); snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language); +#ifdef SUPPORT_SNAPPED_CUPSD + snprintf(ld_library_path, sizeof(ld_library_path), "LD_LIBRARY_PATH=%s", + getenv("LD_LIBRARY_PATH")); +#endif /* SUPPORT_SNAPPED_CUPSD */ snprintf(path, sizeof(path), "PATH=%s", Path); if (ppdfile) snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile); @@ -1122,6 +1149,26 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ envp[5] = cups_serverroot; envp[6] = lang; envp[7] = path; +#ifdef SUPPORT_SNAPPED_CUPSD + envp[8] = ld_library_path; + envp[9] = ppd; + envp[10] = printer_info; + envp[11] = printer_location; + envp[12] = printer_name; + envp[13] = rip_max_cache; + envp[14] = userenv; + envp[15] = "CHARSET=utf-8"; + envp[16] = fontconfig_file; + envp[17] = fontconfig_path; + envp[18] = fontconfig_sysroot; + if (final_content_type[0]) + { + envp[19] = final_content_type; + envp[20] = NULL; + } + else + envp[19] = NULL; +#else envp[8] = ppd; envp[9] = printer_info; envp[10] = printer_location; @@ -1136,6 +1183,7 @@ exec_filters(mime_type_t *srctype, /* I - Source type */ } else envp[15] = NULL; +#endif /* SUPPORT_SNAPPED_CUPSD */ for (i = 0; argv[i]; i ++) fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); @@ -1467,7 +1515,14 @@ read_cups_files_conf( cupsFileClose(fp); } + /* Set the PATH environment variable for external executables, pass + through the PATH from the environment in which cupsd was called + if we are running confined in a Snap */ +#ifdef SUPPORT_SNAPPED_CUPSD + snprintf(line, sizeof(line), "%s/filter:%s:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin, getenv("PATH")); +#else snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin); +#endif /* SUPPORT_SNAPPED_CUPSD */ set_string(&Path, line); return (0); diff --git a/scheduler/env.c b/scheduler/env.c index 71ab98d6b8..71af88416b 100644 --- a/scheduler/env.c +++ b/scheduler/env.c @@ -114,11 +114,14 @@ cupsdSetEnv(const char *name, /* I - Name of variable */ return; /* - * Do not allow dynamic linker variables when running as root... + * Do not allow dynamic linker variables when running as root and + * not being confined in a Snap... */ +#ifndef SUPPORT_SNAPPED_CUPSD if (!RunUser && (!strncmp(name, "DYLD_", 5) || !strncmp(name, "LD_", 3))) return; +#endif /* !SUPPORT_SNAPPED_CUPSD */ /* * See if this variable has already been defined... @@ -208,9 +211,19 @@ cupsdUpdateEnv(void) set_if_undefined("LD_LIBRARY_PATH", NULL); set_if_undefined("LD_PRELOAD", NULL); set_if_undefined("NLSPATH", NULL); + /* Only if cupsd is confined in a Snap we pass the PATH environment + variable on for external executables we call */ if (find_env("PATH") < 0) - cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR - ":/bin:/usr/bin", ServerBin); + { +#ifdef SUPPORT_SNAPPED_CUPSD + char *value; + if ((value = getenv("PATH")) != NULL) + cupsdSetEnvf("PATH", "%s/filter:%s", ServerBin, value); + else +#endif /* SUPPORT_SNAPPED_CUPSD */ + cupsdSetEnvf("PATH", "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR + ":/bin:/usr/bin", ServerBin); + } set_if_undefined("SERVER_ADMIN", ServerAdmin); set_if_undefined("SHLIB_PATH", NULL); set_if_undefined("SOFTWARE", CUPS_MINIMAL);