From f7157d3f9a751fc71729546f4a3c1439792ed901 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Mon, 28 Nov 2022 16:45:42 -0600 Subject: [PATCH] Support subordinate user and group IDs on enterprise set-ups On enterprise FreeIPA set-ups, the subordinate user and group IDs are provided by SSSD's sss plugin for the GNU Name Service Switch (or NSS) functionality of the GNU C Library. They are not listed in /etc/subuid and /etc/subgid. The CGO interaction with libsubid.so is loosely based on 'readSubid' in github.com/containers/storage/pkg/idtools [1]. Unlike 'readSubid', this code considers the absence of any range (ie., nRanges == 0) to be an error as well. More importantly, this code uses dlopen(3) and friends to dynamically load the symbols from libsubid.so, instead of linking to libsubid.so at build-time and having the dependency noted in the /usr/bin/toolbox binary. This is done because libsubid.so itself depends on several other shared libraries, and indirect dependencies can't be influenced by the RUNPATH [2] embedded in the /usr/bin/toolbox binary [3]. Hence, when the binary is used inside Toolbx containers (eg., as the entry point), those indirect dependencies won't be picked from the host's runtime against which the binary was built. This can render the binary useless due to ABI compatibility issues. Using dlopen(3) avoids this problem, especially because libsubid.so is only used when running on the host. Version 4 of the libsubid.so API/ABI [4] was released in Shadow 4.10, which is newer than the versions shipped on RHEL 8 and Debian 10 [5], and even that newer version had some problems [6]. Therefore, support for older versions, with the relevant workarounds, is necessary. This code doesn't set the public variables Prog and shadow_logfd that older Shadow versions used to expect for logging, because from Shadow 4.9 onwards there's a separate function [4,7] to specify these. This can be changed if there are libsubid.so versions in the wild that really do need those public variables to be set. Finally, ISO C99 is required because of the use of in the libsubid.so API. Some changes by Debarshi Ray. [1] https://github.com/containers/storage/blob/main/pkg/idtools/idtools_supported.go [2] https://man7.org/linux/man-pages/man8/ld.so.8.html [3] Commit 6063eb27b9893994 https://github.com/containers/toolbox/issues/821 [4] Shadow commit 32f641b207f6ddff https://github.com/shadow-maint/shadow/commit/32f641b207f6ddff https://github.com/shadow-maint/shadow/issues/443 [5] https://packages.debian.org/source/buster/shadow [6] Shadow commit 79157cbad87f42cd https://github.com/shadow-maint/shadow/commit/79157cbad87f42cd https://github.com/shadow-maint/shadow/issues/465 [7] Shadow commit 2b22a6909dba60d https://github.com/shadow-maint/shadow/commit/2b22a6909dba60d https://github.com/shadow-maint/shadow/issues/325 https://github.com/containers/toolbox/issues/1074 Signed-off-by: Martin Jackson --- meson.build | 3 + playbooks/dependencies-centos-9-stream.yaml | 3 +- playbooks/dependencies-fedora.yaml | 3 +- src/cmd/root.go | 49 ++------ src/meson.build | 1 + src/pkg/utils/utils_cgo.go | 131 ++++++++++++++++++++ 6 files changed, 148 insertions(+), 42 deletions(-) create mode 100644 src/pkg/utils/utils_cgo.go diff --git a/meson.build b/meson.build index 9c2138e25..9a7b1f3b0 100644 --- a/meson.build +++ b/meson.build @@ -3,6 +3,7 @@ project( 'c', version: '0.0.99.3', license: 'ASL 2.0', + default_options: 'c_std=c99', meson_version: '>= 0.58.0', ) @@ -13,6 +14,8 @@ if not cc.has_argument('-print-file-name=libc.so') error('C compiler does not support the -print-file-name argument.') endif +subid_dep = cc.find_library('subid', has_headers: ['shadow/subid.h']) + go = find_program('go') go_md2man = find_program('go-md2man') podman = find_program('podman') diff --git a/playbooks/dependencies-centos-9-stream.yaml b/playbooks/dependencies-centos-9-stream.yaml index 132567b2d..6b59f72c6 100644 --- a/playbooks/dependencies-centos-9-stream.yaml +++ b/playbooks/dependencies-centos-9-stream.yaml @@ -10,6 +10,7 @@ - ninja-build - openssl - podman + - shadow-utils-subid-devel - skopeo - systemd - udisks2 @@ -50,7 +51,7 @@ chdir: '{{ zuul.project.src_dir }}' - name: Check versions of crucial packages - command: rpm -qa ShellCheck codespell *kernel* *glibc* golang podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper + command: rpm -qa ShellCheck codespell *kernel* *glibc* golang shadow-utils-subid-devel podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper - name: Show podman versions command: podman version diff --git a/playbooks/dependencies-fedora.yaml b/playbooks/dependencies-fedora.yaml index b8500a056..62a1dfa48 100644 --- a/playbooks/dependencies-fedora.yaml +++ b/playbooks/dependencies-fedora.yaml @@ -32,6 +32,7 @@ - ninja-build - openssl - podman + - shadow-utils-subid-devel - skopeo - systemd - udisks2 @@ -50,7 +51,7 @@ chdir: '{{ zuul.project.src_dir }}' - name: Check versions of crucial packages - command: rpm -qa ShellCheck codespell *kernel* *glibc* golang podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper + command: rpm -qa ShellCheck codespell *kernel* *glibc* golang shadow-utils-subid-devel podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper - name: Show podman versions command: podman version diff --git a/src/cmd/root.go b/src/cmd/root.go index 828a9d4a0..e8448bebb 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -17,7 +17,6 @@ package cmd import ( - "bufio" "errors" "fmt" "io/ioutil" @@ -141,17 +140,16 @@ func preRun(cmd *cobra.Command, args []string) error { logrus.Debugf("Running on a cgroups v%d host", cgroupsVersion) if currentUser.Uid != "0" { - logrus.Debugf("Checking if /etc/subgid and /etc/subuid have entries for user %s", - currentUser.Username) + logrus.Debugf("Looking for sub-UID and sub-GID ranges for user %s", currentUser.Username) - if _, err := validateSubIDFile("/etc/subuid"); err != nil { - logrus.Debugf("Validating sub-ID file /etc/subuid: %s", err) - return newSubIDFileError() + if _, err := utils.ValidateSubIDRange(currentUser, true); err != nil { + logrus.Debugf("Validating sub-UID range: %s", err) + return newSubIDError() } - if _, err := validateSubIDFile("/etc/subgid"); err != nil { - logrus.Debugf("Validating sub-ID file /etc/subgid: %s", err) - return newSubIDFileError() + if _, err := utils.ValidateSubIDRange(currentUser, false); err != nil { + logrus.Debugf("Validating sub-GID range: %s", err) + return newSubIDError() } } } @@ -321,9 +319,9 @@ func migrate() error { return nil } -func newSubIDFileError() error { +func newSubIDError() error { var builder strings.Builder - fmt.Fprintf(&builder, "/etc/subgid and /etc/subuid don't have entries for user %s\n", currentUser.Username) + fmt.Fprintf(&builder, "Missing subuid and/or subgid ranges for user %s\n", currentUser.Username) fmt.Fprintf(&builder, "See the podman(1), subgid(5), subuid(5) and usermod(8) manuals for more\n") fmt.Fprintf(&builder, "information.") @@ -393,32 +391,3 @@ func setUpLoggers() error { return nil } - -func validateSubIDFile(path string) (bool, error) { - if utils.IsInsideContainer() { - panic("cannot validate sub-IDs inside container") - } - - logrus.Debugf("Validating sub-ID file %s", path) - - file, err := os.Open(path) - if err != nil { - return false, fmt.Errorf("failed to open: %w", err) - } - - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - - prefixes := []string{currentUser.Username + ":", currentUser.Uid + ":"} - - for scanner.Scan() { - line := scanner.Text() - for _, prefix := range prefixes { - if strings.HasPrefix(line, prefix) { - return true, nil - } - } - } - - return false, fmt.Errorf("failed to find an entry for user %s", currentUser.Username) -} diff --git a/src/meson.build b/src/meson.build index 88b81d35a..19e1b29be 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ sources = files( 'pkg/shell/shell.go', 'pkg/utils/errors.go', 'pkg/utils/utils.go', + 'pkg/utils/utils_cgo.go', 'pkg/version/version.go', ) diff --git a/src/pkg/utils/utils_cgo.go b/src/pkg/utils/utils_cgo.go new file mode 100644 index 000000000..b99561511 --- /dev/null +++ b/src/pkg/utils/utils_cgo.go @@ -0,0 +1,131 @@ +/* + * Copyright © 2019 – 2022 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "errors" + "fmt" + "os/user" + "unsafe" +) + +/* +#cgo LDFLAGS: -ldl + +#include +#include +#include + +#include + +#include + +#if SUBID_ABI_MAJOR < 4 +# define subid_get_uid_ranges get_subuid_ranges +# define subid_get_gid_ranges get_subgid_ranges +#endif + +#define TOOLBOX_STRINGIFY_HELPER(s) #s +#define TOOLBOX_STRINGIFY(s) TOOLBOX_STRINGIFY_HELPER (s) + +typedef bool (*ToolboxSubidInitFunc) (const char *progname, FILE *logfd); +typedef int (*ToolboxSubidGetRangesFunc) (const char *owner, struct subid_range **ranges); + +const char *TOOLBOX_LIBSUBID = "libsubid.so." TOOLBOX_STRINGIFY (SUBID_ABI_VERSION); + +const char *TOOLBOX_LIBSUBID_INIT = "libsubid_init"; +const char *TOOLBOX_SUBID_INIT = "subid_init"; + +const char *TOOLBOX_SUBID_GET_UID_RANGES_SYMBOL = TOOLBOX_STRINGIFY (subid_get_uid_ranges); +const char *TOOLBOX_SUBID_GET_GID_RANGES_SYMBOL = TOOLBOX_STRINGIFY (subid_get_gid_ranges); + +void +toolbox_subid_init (void *subid_init_func) +{ + (* (ToolboxSubidInitFunc) subid_init_func) (NULL, NULL); +} + +int +toolbox_subid_get_id_ranges (void *subid_get_id_ranges_func, const char *owner, struct subid_range **ranges) +{ + int ret_val = 0; + + ret_val = (* (ToolboxSubidGetRangesFunc) subid_get_id_ranges_func) (owner, ranges); + return ret_val; +} +*/ +import "C" + +func ValidateSubIDRange(user *user.User, isUser bool) (bool, error) { + if IsInsideContainer() { + panic("cannot validate subordinate IDs inside container") + } + + if user.Username == "ALL" { + return false, errors.New("username ALL not supported") + } + + libsubid := C.dlopen(C.TOOLBOX_LIBSUBID, C.RTLD_LAZY) + if libsubid == nil { + filename := C.GoString(C.TOOLBOX_LIBSUBID) + return false, fmt.Errorf("cannot dlopen(3) %s", filename) + } + + defer C.dlclose(libsubid) + + subid_init := C.dlsym(libsubid, C.TOOLBOX_SUBID_INIT) + if subid_init == nil { + subid_init = C.dlsym(libsubid, C.TOOLBOX_LIBSUBID_INIT) + } + + if subid_init != nil { + C.toolbox_subid_init(subid_init) + } + + var cSubidGetIDRangesSymbol *C.char + if isUser { + cSubidGetIDRangesSymbol = C.TOOLBOX_SUBID_GET_UID_RANGES_SYMBOL + } else { + cSubidGetIDRangesSymbol = C.TOOLBOX_SUBID_GET_GID_RANGES_SYMBOL + } + + subid_get_id_ranges := C.dlsym(libsubid, cSubidGetIDRangesSymbol) + if subid_get_id_ranges == nil { + subidGetIDRangesSymbol := C.GoString(cSubidGetIDRangesSymbol) + return false, fmt.Errorf("cannot dlsym(3) %s", subidGetIDRangesSymbol) + } + + cUsername := C.CString(user.Username) + defer C.free(unsafe.Pointer(cUsername)) + + var cRanges *C.struct_subid_range + defer C.free(unsafe.Pointer(cRanges)) + + nRanges := C.toolbox_subid_get_id_ranges(subid_get_id_ranges, cUsername, &cRanges) + if nRanges <= 0 { + cUid := C.CString(user.Uid) + defer C.free(unsafe.Pointer(cUid)) + + nRanges = C.toolbox_subid_get_id_ranges(subid_get_id_ranges, cUid, &cRanges) + } + + if nRanges <= 0 { + return false, errors.New("cannot read subids") + } + + return true, nil +}