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 fc04da44c..98eec9db1 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,11 @@ 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-GID and sub-UID 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 := validateSubIDFile("/etc/subgid"); err != nil { - logrus.Debugf("Validating sub-ID file /etc/subgid: %s", err) - return newSubIDFileError() + if _, err := utils.ValidateSubIDRanges(currentUser); err != nil { + logrus.Debugf("Validating sub-GID and sub-UID ranges: %s", err) + return newSubIDError() } } } @@ -321,9 +314,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 subgid and/or subuid 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 +386,3 @@ func setUpLoggers() error { return nil } - -func validateSubIDFile(path string) (bool, error) { - if utils.IsInsideContainer() { - panic("cannot validate sub-IDs inside container") - } - - file, err := os.Open(path) - if err != nil { - return false, fmt.Errorf("failed to open: %w", err) - } - - defer file.Close() - - 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..6874a9043 --- /dev/null +++ b/src/pkg/utils/utils_cgo.go @@ -0,0 +1,140 @@ +/* + * 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_gid_ranges get_subgid_ranges +# define subid_get_uid_ranges get_subuid_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_GID_RANGES_SYMBOL = TOOLBOX_STRINGIFY (subid_get_gid_ranges); +const char *TOOLBOX_SUBID_GET_UID_RANGES_SYMBOL = TOOLBOX_STRINGIFY (subid_get_uid_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, libsubid unsafe.Pointer, cSubidGetIDRangesSymbol *C.char) (bool, error) { + 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 +} + +func ValidateSubIDRanges(user *user.User) (bool, error) { + if IsInsideContainer() { + panic("cannot validate subordinate IDs inside container") + } + + if user == nil { + panic("cannot validate subordinate IDs when user is nil") + } + + 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) + } + + if _, err := validateSubIDRange(user, libsubid, C.TOOLBOX_SUBID_GET_GID_RANGES_SYMBOL); err != nil { + return false, err + } + + if _, err := validateSubIDRange(user, libsubid, C.TOOLBOX_SUBID_GET_UID_RANGES_SYMBOL); err != nil { + return false, err + } + + return true, nil +} diff --git a/test/system/104-run.bats b/test/system/104-run.bats index a7080de56..a56018b4d 100644 --- a/test/system/104-run.bats +++ b/test/system/104-run.bats @@ -184,7 +184,7 @@ teardown() { create_default_container # File descriptors 3 and 4 are reserved by Bats. - run --separate-stderr $TOOLBOX run --preserve-fds 3 readlink /proc/self/fd/5 5>/dev/null + run --separate-stderr $TOOLBOX run --preserve-fds 3 readlink /proc/self/fd/6 6>/dev/null assert_success assert_line --index 0 "/dev/null" @@ -336,12 +336,12 @@ teardown() { create_default_container # File descriptors 3 and 4 are reserved by Bats. - run -125 --separate-stderr $TOOLBOX run --preserve-fds 3 readlink /proc/self/fd/5 + run -125 --separate-stderr $TOOLBOX run --preserve-fds 3 readlink /proc/self/fd/6 assert_failure assert [ ${#lines[@]} -eq 0 ] lines=("${stderr_lines[@]}") - assert_line --index 0 "Error: file descriptor 5 is not available - the preserve-fds option requires that file descriptors must be passed" + assert_line --index 0 "Error: file descriptor 6 is not available - the preserve-fds option requires that file descriptors must be passed" assert_line --index 1 "Error: failed to invoke 'podman exec' in container $default_container_name" assert [ ${#stderr_lines[@]} -eq 2 ] }