diff options
70 files changed, 1575 insertions, 225 deletions
@@ -1,4 +1,5 @@ *.o +*.pyc Makefile libpseudo.so pseudo_wrapfuncs.* @@ -14,3 +15,5 @@ pseudo_ports.h templatefile.pyc func_deps.mk port_deps.mk +test/test-* +!test/test-*.* diff --git a/ChangeLog.txt b/ChangeLog.txt index 329b262..f681596 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,7 @@ +2019-08-02: + * (seebs) Pass flags & O_NOFOLLOW, also use that to influence + stat types. Note. &. Not |. + 2019-08-01: * (seebs) Pass flags|O_NOFOLLOW on when resolving paths with openat. diff --git a/Makefile.in b/Makefile.in index b43d2d6..48fdbd2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -42,7 +42,7 @@ LOCALSTATE=var/pseudo BINDIR=$(PREFIX)/$(BIN) LOCALSTATEDIR=$(PREFIX)/$(LOCALSTATE) -CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra +CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra -Wno-deprecated-declarations CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE $(ARCH_FLAGS) CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS) $(EPOLL) CFLAGS_DEBUG=-O2 -g @@ -55,6 +55,7 @@ GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN))) SOURCES=$(wildcard *.c) OBJS=$(subst .c,.o,$(SOURCES)) +TESTS=$(patsubst %.c,%,$(wildcard test/*.c)) SHOBJS=pseudo_tables.o pseudo_util.o DBOBJS=pseudo_db.o @@ -74,8 +75,11 @@ TABLES=table_templates/pseudo_tables.c table_templates/pseudo_tables.h all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) -test: all | $(BIN) $(LIB) $(LOCALSTATE) - @./run_tests.sh -v +test: all $(TESTS) | $(BIN) $(LIB) + ./run_tests.sh -v + +test/%: test/%.c + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $@ $< install-lib: $(LIBPSEUDO) mkdir -p $(DESTDIR)$(LIBDIR) @@ -91,7 +95,7 @@ install-data: install: all install-lib install-bin install-data -$(BIN) $(LIB) $(LOCALSTATE): +$(BIN) $(LIB): mkdir -p $@ pseudo: $(PSEUDO) @@ -165,7 +169,8 @@ pseudo_profile: Makefile pseudo_profile.c tables wrappers $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o pseudo_profile pseudo_profile.c clean: - rm -f *.o *.so $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) \ + rm -f *.o $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) \ + $(TESTS) \ pseudo_wrapfuncs.h pseudo_wrapfuncs.c \ pseudo_wrapper_table.c \ pseudo_tables.c pseudo_tables.h \ @@ -176,8 +181,8 @@ clean: touch port_deps.mk func_deps.mk distclean: clean - rm -f Makefile - rm -rf ./$(BIN) ./$(LIB) ./$(LOCALSTATE) + rm -f Makefile port_deps.mk func_deps.mk + rm -rf ./$(BIN) ./$(LIB) ./$(LOCALSTATE) ./__pycache__ @echo "WARNING: Makefile has been removed. You must reconfigure to do anything else." nuke: distclean @@ -1,16 +1,8 @@ -pseudo -- an analogue to sudo +pseudo -- an analogue to sudo and an alternative to fakeroot -IMPORTANT NOTE: +The official home for pseudo's git repository is: -As of this writing, the official home for pseudo's git repository has -changed to: - git://git.yoctoproject.org/pseudo - -The old site at: - https://github.com/wrpseudo/pseudo - -is no longer the "real" master, although I'll probably try to keep it in -sync. + https://git.yoctoproject.org/pseudo OVERVIEW: @@ -78,6 +70,7 @@ commentary. ACKNOWLEDGEMENTS: +Peter Seebach: My various coworkers, both engineering and management, made this possible. While I did most of the actual typing, this code has benefitted greatly from detailed code reviews, excellent reproducers for bugs, and the @@ -86,9 +79,19 @@ deal of fun, and I'm pretty happy that we're finally ready to make it available for other people to look at. +MAINTAINERS: + +Richard Purdie +Mark Hatle + + CONTACT: -Discussions and patches should be directed at the openembedded-core mailing -list at openembedded-core at lists.openembedded.org. More information at -https://www.openembedded.org/wiki/Mailing_lists. Bugs should be filed with -the Yocto project at https://bugzilla.yoctoproject.org/. +Patches should be directed at the yocto-patches mailing list at yocto-patches +at lists.yoctoproject.org with [pseudo] in the subject line. Bugs should be +filed with the Yocto project at https://bugzilla.yoctoproject.org/. + +The repository can be configured to do this correctly with: + + git config format.subjectprefix "pseudo] [PATCH" + git config sendemail.to yocto-patches@lists.yoctoproject.org diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7ccecc1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +How to Report a Potential Vulnerability? +======================================== + +If you would like to report a public issue (for example, one with a released +CVE number), please report it using the +[https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Security Security Bugzilla]. +If you have a patch ready, submit it following the same procedure as any other +patch as described in README.md. + +If you are dealing with a not-yet released or urgent issue, please send a +message to security AT yoctoproject DOT org, including as many details as +possible: the layer or software module affected, the recipe and its version, +and any example code, if available. diff --git a/enums/op.in b/enums/op.in index 61ee666..5b5e21b 100644 --- a/enums/op.in +++ b/enums/op.in @@ -27,3 +27,4 @@ remove-xattr, 1 set-xattr, 0 create-xattr, 1 replace-xattr, 1 +closefrom, 0 diff --git a/enums/res.in b/enums/res.in index 435338f..b0096b4 100644 --- a/enums/res.in +++ b/enums/res.in @@ -2,3 +2,4 @@ res: RESULT succeed fail error +abort @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (c) 2008-2010, 2013 Wind River Systems, Inc. # diff --git a/makewrappers b/makewrappers index e84607d..cf5ad60 100755 --- a/makewrappers +++ b/makewrappers @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (c) 2008-2011,2013 Wind River Systems, Inc. # @@ -11,6 +11,7 @@ import glob import sys import re import os.path +import platform import string import subprocess from templatefile import TemplateFile @@ -228,6 +229,8 @@ class Function: self.real_func = None self.paths_to_munge = [] self.specific_dirfds = {} + self.fd_arg = False + self.noignore_path = False self.hand_wrapped = None self.async_skip = None # used for the copyright date when creating stub functions @@ -267,6 +270,11 @@ class Function: self.flags = '(flags & AT_SYMLINK_NOFOLLOW)' elif arg.name.endswith('path'): self.paths_to_munge.append(arg.name) + elif arg.name == 'fd': + self.fd_arg = "fd" + elif arg.name == 'filedes': + self.fd_arg = "filedes" + # pick default values if self.type == 'void': @@ -283,10 +291,18 @@ class Function: # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW if self.comments: - modifiers = self.comments.split(', ') - for mod in modifiers: - key, value = mod.split('=') - value = value.rstrip() + # Build a dictionary of key=value, key=value pairs + modifiers = dict(mod.split("=") for mod in self.comments.split(',')) + # Strip all leading/trailing whitespace + modifiers = {k.strip():v.strip() for k, v in modifiers.items()} + + arch = "-" + platform.machine() + # Sorted so that versions-foo appear after versions, so overrides are easy + for key in sorted(modifiers): + value = modifiers[key] + # If the key is version-arm64 and we're on arm64 then rename this to version + if key.endswith(arch): + key = key.replace(arch, "") setattr(self, key, value) def maybe_inode64(self): @@ -356,11 +372,39 @@ class Function: prefix = path[:-4] if prefix not in self.specific_dirfds: prefix = '' + if self.dirfd != "AT_FDCWD" and "flags" in self.flags \ + and "AT_SYMLINK_NOFOLLOW" in self.flags: + fix_paths.append( + # Reference the variable inside a noop inline-asm to stop + # the compiler from eliminating the null pointer check + # on parameters marked nonnull + "asm(\"\" : \"+r\"(%s));" + "if (%s && !*%s && (flags & AT_EMPTY_PATH))\n" + "\t\t\tflags |= AT_SYMLINK_NOFOLLOW;" % (path, path, path)) fix_paths.append( "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" % (path, prefix, self.dirfd, path, self.flags)) return "\n\t\t".join(fix_paths) + def ignore_paths(self): + if self.noignore_path: + return "0" + + mainpath = None + if "oldpath" in self.paths_to_munge: + mainpath = "oldpath" + elif "newpath" in self.paths_to_munge: + mainpath = "newpath" + elif "path" in self.paths_to_munge: + mainpath = "path" + + if mainpath: + return "pseudo_client_ignore_path(%s)" % mainpath + if self.fd_arg: + return "pseudo_client_ignore_fd(%s)" % self.fd_arg + + return "0" + def real_predecl(self): if self.real_func: return self.decl().replace(self.name, self.real_func, 1) + ";" @@ -567,7 +611,7 @@ def process_wrapfuncs(port): func.directory = directory funcs[func.name] = func sys.stdout.write(".") - except Exception(e): + except Exception as e: print("Parsing failed:", e) exit(1) funclist.close() diff --git a/ports/linux/guts/close_range.c b/ports/linux/guts/close_range.c new file mode 100644 index 0000000..4bd2fe1 --- /dev/null +++ b/ports/linux/guts/close_range.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Richard Purdie + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * int close_range(unsigned int lowfd, unsigned int maxfd, int flags) + * int rc = -1; + */ + + (void) lowfd; + (void) maxfd; + (void) flags; + /* for now pretend the kernel doesn't support it regardless + which users are supposed to be able to handle */ + errno = ENOSYS; + rc = -1; + +/* return rc; + * } + */ diff --git a/ports/linux/guts/closefrom.c b/ports/linux/guts/closefrom.c new file mode 100644 index 0000000..1350506 --- /dev/null +++ b/ports/linux/guts/closefrom.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2021 Richard Purdie + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * void closefrom(int fd) + */ + pseudo_msg_t *msg; + /* this cleans up internal tables, and shouldn't make it to the server. Avoids pseudo's internal fds */ + msg = pseudo_client_op(OP_CLOSEFROM, 0, fd, -1, 0, 0); + /* fds between fd and msg->fd are closed within the above function avoiding pseudo's own fds */ + real_closefrom(msg->fd); + +/* return; + * } + */ diff --git a/ports/linux/guts/fcntl.c b/ports/linux/guts/fcntl.c index 4dd9796..ffb50be 100644 --- a/ports/linux/guts/fcntl.c +++ b/ports/linux/guts/fcntl.c @@ -8,6 +8,22 @@ * wrap_fcntl(int fd, int cmd, ...struct flock *lock) { * int rc = -1; */ +#if !defined(F_GETPIPE_SZ) +#define F_GETPIPE_SZ (1032) +#endif + +#if F_GETPIPE_SZ != 1032 +#error System F_GETPIPE_SZ has unexpected value +#endif + +#if !defined(F_SETPIPE_SZ) +#define F_SETPIPE_SZ (1031) +#endif + +#if F_SETPIPE_SZ != 1031 +#error System F_SETPIPE_SZ has unexpected value +#endif + long arg; int save_errno; @@ -31,12 +47,17 @@ } errno = save_errno; break; + case F_SETPIPE_SZ: + /* actually do something */ + rc = real_fcntl(fd, cmd, arg); + break; /* no argument: */ case F_GETFD: case F_GETFL: case F_GETOWN: case F_GETSIG: case F_GETLEASE: + case F_GETPIPE_SZ: rc = real_fcntl(fd, cmd); break; /* long argument */ @@ -52,6 +73,11 @@ case F_GETLK: case F_SETLK: case F_SETLKW: +#ifdef F_OFD_GETLK + case F_OFD_GETLK: + case F_OFD_SETLK: + case F_OFD_SETLKW: +#endif rc = real_fcntl(fd, cmd, lock); break; #if defined(F_GETLK64) && (F_GETLK64 != F_GETLK) diff --git a/ports/linux/guts/fcntl64.c b/ports/linux/guts/fcntl64.c new file mode 100644 index 0000000..99de43d --- /dev/null +++ b/ports/linux/guts/fcntl64.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static int + * wrap_fcntl64(int fd, int cmd, ...struct flock *lock) { + * int rc = -1; + */ +#if !defined(F_GETPIPE_SZ) +#define F_GETPIPE_SZ (1032) +#endif + +#if F_GETPIPE_SZ != 1032 +#error System F_GETPIPE_SZ has unexpected value +#endif + +#if !defined(F_SETPIPE_SZ) +#define F_SETPIPE_SZ (1031) +#endif + +#if F_SETPIPE_SZ != 1031 +#error System F_SETPIPE_SZ has unexpected value +#endif + + long arg; + int save_errno; + + /* we don't know whether we need lock or arg; grab both, which + * should be safe enough on Linuxy systems. */ + va_start(ap, cmd); + arg = va_arg(ap, long); + va_end(ap); + + switch (cmd) { + case F_DUPFD: +#ifdef F_DUPFD_CLOEXEC + case F_DUPFD_CLOEXEC: +#endif + /* actually do something */ + rc = real_fcntl64(fd, cmd, arg); + save_errno = errno; + if (rc != -1) { + pseudo_debug(PDBGF_OP, "fcntl64_dup: %d->%d\n", fd, rc); + pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0); + } + errno = save_errno; + break; + case F_SETPIPE_SZ: + /* actually do something */ + rc = real_fcntl64(fd, cmd, arg); + break; + /* no argument: */ + case F_GETFD: + case F_GETFL: + case F_GETOWN: + case F_GETSIG: + case F_GETLEASE: + case F_GETPIPE_SZ: + rc = real_fcntl64(fd, cmd); + break; + /* long argument */ + case F_SETFD: + case F_SETFL: + case F_SETOWN: + case F_SETSIG: + case F_SETLEASE: + case F_NOTIFY: + rc = real_fcntl64(fd, cmd, arg); + break; + /* struct flock * argument */ + case F_GETLK: + case F_SETLK: + case F_SETLKW: +#ifdef F_OFD_GETLK + case F_OFD_GETLK: + case F_OFD_SETLK: + case F_OFD_SETLKW: +#endif + rc = real_fcntl64(fd, cmd, lock); + break; +#if defined(F_GETLK64) && (F_GETLK64 != F_GETLK) + /* the cast is safe, all struct pointers must smell the same */ + case F_GETLK64: + case F_SETLK64: + case F_SETLKW64: + rc = real_fcntl64(fd, cmd, (struct flock64 *) lock); + break; +#endif + default: + pseudo_diag("unknown fcntl64 argument %d, assuming long argument.\n", + cmd); + rc = real_fcntl64(fd, cmd, arg); + break; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/fstatat.c b/ports/linux/guts/fstatat.c new file mode 100644 index 0000000..3267641 --- /dev/null +++ b/ports/linux/guts/fstatat.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Linux Foundation; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * int fstatat(int dirfd, const char *path, struct stat *buf, int flags) + * int rc = -1; + */ + + rc = wrap___fxstatat(_STAT_VER, dirfd, path, buf, flags); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/fstatat64.c b/ports/linux/guts/fstatat64.c new file mode 100644 index 0000000..c981e14 --- /dev/null +++ b/ports/linux/guts/fstatat64.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Linux Foundation; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * int fstatat64(int dirfd, const char *path, struct stat64 *buf, int flags) + * int rc = -1; + */ + + rc = wrap___fxstatat64(_STAT_VER, dirfd, path, buf, flags); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/lchmod.c b/ports/linux/guts/lchmod.c new file mode 100644 index 0000000..da330a7 --- /dev/null +++ b/ports/linux/guts/lchmod.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2021 Linux Foundation + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static int + * wrap_lchmod(const char *path, mode_t mode) { + */ + + rc = wrap_fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/mkostemp64.c b/ports/linux/guts/mkostemp64.c new file mode 100644 index 0000000..502211b --- /dev/null +++ b/ports/linux/guts/mkostemp64.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static int + * wrap_mkstemp64(char *template, int oflags) { + * int rc = -1; + */ + struct stat64 buf; + int save_errno; + size_t len; + char *tmp_template; + + if (!template) { + errno = EFAULT; + return 0; + } + + len = strlen(template); + tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); + + if (!tmp_template) { + errno = ENOENT; + return -1; + } + + /* mkstemp64 wrapper uses this code and mkostemp64 not present in some glibc versions */ + if (oflags == 0) + rc = real_mkstemp64(tmp_template); + else + rc = real_mkostemp64(tmp_template, oflags); + + if (rc != -1) { + save_errno = errno; + + if (real___fxstat64(_STAT_VER, rc, &buf) != -1) { + real_fchmod(rc, PSEUDO_FS_MODE(0600, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n", + rc, strerror(errno)); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0); + } + errno = save_errno; + } + /* mkstemp only changes the XXXXXX at the end. */ + memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); +/* return rc; + * } + */ diff --git a/ports/linux/guts/mkstemp64.c b/ports/linux/guts/mkstemp64.c index aa7bb58..487f256 100644 --- a/ports/linux/guts/mkstemp64.c +++ b/ports/linux/guts/mkstemp64.c @@ -8,42 +8,9 @@ * wrap_mkstemp64(char *template) { * int rc = -1; */ - struct stat64 buf; - int save_errno; - size_t len; - char *tmp_template; + /* mkstemp64() is just like mkostemp64() with no flags */ + rc = wrap_mkostemp64(template, 0); - if (!template) { - errno = EFAULT; - return 0; - } - - len = strlen(template); - tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); - - if (!tmp_template) { - errno = ENOENT; - return -1; - } - - rc = real_mkstemp64(tmp_template); - - if (rc != -1) { - save_errno = errno; - - if (real___fxstat64(_STAT_VER, rc, &buf) != -1) { - real_fchmod(rc, PSEUDO_FS_MODE(0600, 0)); - pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf); - pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf); - } else { - pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n", - rc, strerror(errno)); - pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0); - } - errno = save_errno; - } - /* mkstemp only changes the XXXXXX at the end. */ - memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); /* return rc; * } */ diff --git a/ports/linux/guts/openat.c b/ports/linux/guts/openat.c index 673ea6e..656ac2b 100644 --- a/ports/linux/guts/openat.c +++ b/ports/linux/guts/openat.c @@ -55,9 +55,13 @@ if (flags & O_CREAT) { save_errno = errno; #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS - rc = real___xstat64(_STAT_VER, path, &buf); + if (flags & O_NOFOLLOW) { + rc = real___lxstat64(_STAT_VER, path, &buf); + } else { + rc = real___xstat64(_STAT_VER, path, &buf); + } #else - rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0); + rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); #endif existed = (rc != -1); if (!existed) @@ -72,9 +76,13 @@ if (!(flags & O_NONBLOCK) && ((flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_RDWR)) { save_errno = errno; #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS - rc = real___xstat64(_STAT_VER, path, &buf); + if (flags & O_NOFOLLOW) { + rc = real___lxstat64(_STAT_VER, path, &buf); + } else { + rc = real___xstat64(_STAT_VER, path, &buf); + } #else - rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0); + rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); #endif if (rc != -1 && S_ISFIFO(buf.st_mode)) { overly_magic_nonblocking = 1; @@ -126,11 +134,17 @@ } #endif #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS - stat_rc = real___xstat64(_STAT_VER, path, &buf); + if (flags & O_NOFOLLOW) { + stat_rc = real___lxstat64(_STAT_VER, path, &buf); + } else { + stat_rc = real___xstat64(_STAT_VER, path, &buf); + } #else - stat_rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0); + stat_rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); #endif + pseudo_debug(PDBGF_FILE, "openat(path %s), flags %o, stat rc %d, stat mode %o\n", + path, flags, stat_rc, buf.st_mode); if (stat_rc != -1) { buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); if (!existed) { diff --git a/ports/linux/nostatx/portdefs.h b/ports/linux/nostatx/portdefs.h new file mode 100644 index 0000000..ded8ff9 --- /dev/null +++ b/ports/linux/nostatx/portdefs.h @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copy of the statx struct to allow a pseudo built on a system without + * statx to work on one with statx and hence work with OE's uninative + */ + +struct statx_timestamp +{ + __int64_t tv_sec; + __uint32_t tv_nsec; + __int32_t __statx_timestamp_pad1[1]; +}; + +struct statx +{ + __uint32_t stx_mask; + __uint32_t stx_blksize; + __uint64_t stx_attributes; + __uint32_t stx_nlink; + __uint32_t stx_uid; + __uint32_t stx_gid; + __uint16_t stx_mode; + __uint16_t __statx_pad1[1]; + __uint64_t stx_ino; + __uint64_t stx_size; + __uint64_t stx_blocks; + __uint64_t stx_attributes_mask; + struct statx_timestamp stx_atime; + struct statx_timestamp stx_btime; + struct statx_timestamp stx_ctime; + struct statx_timestamp stx_mtime; + __uint32_t stx_rdev_major; + __uint32_t stx_rdev_minor; + __uint32_t stx_dev_major; + __uint32_t stx_dev_minor; + __uint64_t __statx_pad2[14]; +}; diff --git a/ports/linux/portdefs.h b/ports/linux/portdefs.h index d419365..9545550 100644 --- a/ports/linux/portdefs.h +++ b/ports/linux/portdefs.h @@ -32,3 +32,24 @@ GLIBC_COMPAT_SYMBOL(memcpy,2.0); #include <linux/capability.h> #include <sys/syscall.h> +#include <sys/prctl.h> +#include <linux/seccomp.h> + +#ifndef _STAT_VER +#if defined (__aarch64__) +#define _STAT_VER 0 +#elif defined (__x86_64__) +#define _STAT_VER 1 +#else +#define _STAT_VER 3 +#endif +#endif +#ifndef _MKNOD_VER +#if defined (__aarch64__) +#define _MKNOD_VER 0 +#elif defined (__x86_64__) +#define _MKNOD_VER 0 +#else +#define _MKNOD_VER 1 +#endif +#endif diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c index cd7e173..7659897 100644 --- a/ports/linux/pseudo_wrappers.c +++ b/ports/linux/pseudo_wrappers.c @@ -57,6 +57,7 @@ int pseudo_capset(cap_user_header_t hdrp, const cap_user_data_t datap) { long syscall(long number, ...) { long rc = -1; + va_list ap; if (!pseudo_check_wrappers() || !real_syscall) { /* rc was initialized to the "failure" value */ @@ -77,11 +78,25 @@ syscall(long number, ...) { (void) number; #endif +#ifdef SYS_seccomp + /* pseudo and seccomp are incompatible as pseudo uses different syscalls + * so pretend to enable seccomp but really do nothing */ + if (number == SYS_seccomp) { + unsigned long cmd; + va_start(ap, number); + cmd = va_arg(ap, unsigned long); + va_end(ap); + if (cmd == SECCOMP_SET_MODE_FILTER) { + return 0; + } + } +#endif + /* gcc magic to attempt to just pass these args to syscall. we have to * guess about the number of args; the docs discuss calling conventions * up to 7, so let's try that? */ - void *res = __builtin_apply((void (*)()) real_syscall, __builtin_apply_args(), sizeof(long) * 7); + void *res = __builtin_apply((void (*)(void)) real_syscall, __builtin_apply_args(), sizeof(long) * 7); __builtin_return(res); } @@ -92,3 +107,44 @@ static long wrap_syscall(long nr, va_list ap) { (void) ap; return -1; } + +int +prctl(int option, ...) { + int rc = -1; + va_list ap; + + if (!pseudo_check_wrappers() || !real_prctl) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("prctl"); + return rc; + } + +#ifdef SECCOMP_SET_MODE_FILTER + /* pseudo and seccomp are incompatible as pseudo uses different syscalls + * so pretend to enable seccomp but really do nothing */ + if (option == PR_SET_SECCOMP) { + unsigned long cmd; + va_start(ap, option); + cmd = va_arg(ap, unsigned long); + va_end(ap); + if (cmd == SECCOMP_SET_MODE_FILTER) { + return 0; + } + } +#endif + + /* gcc magic to attempt to just pass these args to prctl. we have to + * guess about the number of args; the docs discuss calling conventions + * up to 5, so let's try that? + */ + void *res = __builtin_apply((void (*)(void)) real_prctl, __builtin_apply_args(), sizeof(long) * 5); + __builtin_return(res); +} + +/* unused. + */ +static int wrap_prctl(int option, va_list ap) { + (void) option; + (void) ap; + return -1; +} diff --git a/ports/linux/statvfs/guts/statvfs64.c b/ports/linux/statvfs/guts/statvfs64.c new file mode 100644 index 0000000..856d3db --- /dev/null +++ b/ports/linux/statvfs/guts/statvfs64.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * int statvfs64(const char *path, struct statvfs64 *buf) + * int rc = -1; + */ + + rc = real_statvfs64(path, buf); + +/* return rc; + * } + */ diff --git a/ports/linux/statvfs/wrapfuncs.in b/ports/linux/statvfs/wrapfuncs.in index 1afb64d..6a59660 100644 --- a/ports/linux/statvfs/wrapfuncs.in +++ b/ports/linux/statvfs/wrapfuncs.in @@ -1 +1,2 @@ int statvfs(const char *path, struct statvfs *buf); +int statvfs64(const char *path, struct statvfs64 *buf); diff --git a/ports/linux/statx/guts/statx.c b/ports/linux/statx/guts/statx.c new file mode 100644 index 0000000..42aebe5 --- /dev/null +++ b/ports/linux/statx/guts/statx.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Linux Foundation + * Author: Richard Purdie + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * int + * statx(int dirfd, const char *path, int flags, unsigned int mask, struct statx *statxbuf) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno; + + rc = real_statx(dirfd, path, flags, mask, statxbuf); + save_errno = errno; + if (rc == -1) { + return rc; + } + + buf.st_uid = statxbuf->stx_uid; + buf.st_gid = statxbuf->stx_gid; + buf.st_dev = makedev(statxbuf->stx_dev_major, statxbuf->stx_dev_minor); + buf.st_ino = statxbuf->stx_ino; + buf.st_mode = statxbuf->stx_mode; + buf.st_rdev = makedev(statxbuf->stx_rdev_major, statxbuf->stx_rdev_minor); + buf.st_nlink = statxbuf->stx_nlink; + msg = pseudo_client_op(OP_STAT, 0, -1, dirfd, path, &buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_debug(PDBGF_FILE, "statx(path %s), flags %o, stat rc %d, stat uid %o\n", path, flags, rc, statxbuf->stx_uid); + statxbuf->stx_uid = msg->uid; + statxbuf->stx_gid = msg->gid; + statxbuf->stx_mode = msg->mode; + statxbuf->stx_rdev_major = major(msg->rdev); + statxbuf->stx_rdev_minor = minor(msg->rdev); + } else { + pseudo_debug(PDBGF_FILE, "statx(path %s) failed, flags %o, stat rc %d, stat uid %o\n", path, flags, rc, statxbuf->stx_uid); + } + errno = save_errno; +/* return rc; + * } + */ diff --git a/ports/linux/statx/portdefs.h b/ports/linux/statx/portdefs.h new file mode 100644 index 0000000..bf934dc --- /dev/null +++ b/ports/linux/statx/portdefs.h @@ -0,0 +1,6 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-only + * + */ +#include <sys/stat.h> +#include <sys/sysmacros.h> diff --git a/ports/linux/statx/wrapfuncs.in b/ports/linux/statx/wrapfuncs.in new file mode 100644 index 0000000..168234f --- /dev/null +++ b/ports/linux/statx/wrapfuncs.in @@ -0,0 +1 @@ +int statx(int dirfd, const char *path, int flags, unsigned int mask, struct statx *statxbuf); diff --git a/ports/linux/subports b/ports/linux/subports index a29044a..099ea59 100755 --- a/ports/linux/subports +++ b/ports/linux/subports @@ -29,11 +29,12 @@ fi if $port_xattr; then cat > dummy.c <<EOF #include <sys/types.h> -#include <attr/xattr.h> +#include <sys/xattr.h> +#include <attr/attributes.h> int i; EOF if ! ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then - echo >&2 "Warning: Can't compile trivial program using <attr/xattr.h>". + echo >&2 "Warning: Can't compile trivial program using <attr/attributes.h>". echo >&2 " xattr support will require that header." fi echo "linux/xattr" @@ -54,3 +55,18 @@ else fi rm -f dummy.c dummy.o +# For statx, we want a pseudo which can work with OE's uninative, i.e. build on a system without +# statx but work on one with it. We have a header in nostatx to allow this. +cat > dummy.c <<EOF +#define _GNU_SOURCE +#include <sys/stat.h> +struct statx x; +EOF +if ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then + echo "linux/statx" +else + echo "linux/nostatx" + echo "linux/statx" +fi +rm -f dummy.c dummy.o + diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in index 4cdbc9c..97b16c2 100644 --- a/ports/linux/wrapfuncs.in +++ b/ports/linux/wrapfuncs.in @@ -1,41 +1,46 @@ -int open(const char *path, int flags, ...{mode_t mode}); /* flags=flags|O_NOFOLLOW */ +int open(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ char *get_current_dir_name(void); int __xstat(int ver, const char *path, struct stat *buf); int __lxstat(int ver, const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW */ int __fxstat(int ver, int fd, struct stat *buf); +int lchmod(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */ int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */ int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags); -int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags|O_NOFOLLOW */ -int __openat_2(int dirfd, const char *path, int flags); /* flags=flags|O_NOFOLLOW */ +int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ +int __openat_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ int mknod(const char *path, mode_t mode, dev_t dev); /* real_func=pseudo_mknod */ int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev); /* real_func=pseudo_mknodat */ int __xmknod(int ver, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */ int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */ -int fcntl(int fd, int cmd, ...{struct flock *lock}); +int fcntl(int fd, int cmd, ...{struct flock *lock}); /* noignore_path=1 */ +int fcntl64(int fd, int cmd, ...{struct flock *lock}); /* noignore_path=1 */ # just so we know the inums of symlinks char *canonicalize_file_name(const char *filename); int eaccess(const char *path, int mode); -int open64(const char *path, int flags, ...{mode_t mode}); /* flags=flags|O_NOFOLLOW */ -int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags|O_NOFOLLOW */ -int __openat64_2(int dirfd, const char *path, int flags); /* flags=flags|O_NOFOLLOW */ +int open64(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ +int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ +int __openat64_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW, noignore_path=1 */ int creat64(const char *path, mode_t mode); int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat */ int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW */ int fstat(int fd, struct stat *buf); /* real_func=pseudo_fstat */ +int fstatat(int dirfd, const char *path, struct stat *buf, int flags); int stat64(const char *path, struct stat64 *buf); /* real_func=pseudo_stat64 */ int lstat64(const char *path, struct stat64 *buf); /* real_func=pseudo_lstat64, flags=AT_SYMLINK_NOFOLLOW */ int fstat64(int fd, struct stat64 *buf); /* real_func=pseudo_fstat64 */ +int fstatat64(int dirfd, const char *path, struct stat64 *buf, int flags); int __xstat64(int ver, const char *path, struct stat64 *buf); int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLINK_NOFOLLOW */ int __fxstat64(int ver, int fd, struct stat64 *buf); int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags); -FILE *fopen64(const char *path, const char *mode); -int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); -FILE *freopen64(const char *path, const char *mode, FILE *stream); +FILE *fopen64(const char *path, const char *mode); /* noignore_path=1 */ +int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); /* noignore_path=1 */ +FILE *freopen64(const char *path, const char *mode, FILE *stream); /* noignore_path=1 */ int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd); int glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob); int scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)()); int truncate64(const char *path, off64_t length); +int mkostemp64(char *template, int oflags); /* flags=AT_SYMLINK_NOFOLLOW */ int mkstemp64(char *template); /* flags=AT_SYMLINK_NOFOLLOW */ int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups); int setgroups(size_t size, const gid_t *list); @@ -56,3 +61,6 @@ int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbuf int capset(cap_user_header_t hdrp, const cap_user_data_t datap); /* real_func=pseudo_capset */ long syscall(long nr, ...); /* hand_wrapped=1 */ int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); /* flags=AT_SYMLINK_NOFOLLOW */ +int prctl(int option, ...); /* hand_wrapped=1 */ +int close_range(unsigned int lowfd, unsigned int maxfd, int flags); +void closefrom(int fd); diff --git a/ports/linux/xattr/portdefs.h b/ports/linux/xattr/portdefs.h index 56cd3ca..beab7d0 100644 --- a/ports/linux/xattr/portdefs.h +++ b/ports/linux/xattr/portdefs.h @@ -2,5 +2,9 @@ * SPDX-License-Identifier: LGPL-2.1-only * */ -#include <attr/xattr.h> +#include <sys/xattr.h> #include <stdint.h> + +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif diff --git a/ports/linux/xattr/pseudo_wrappers.c b/ports/linux/xattr/pseudo_wrappers.c index 590af30..0b65920 100644 --- a/ports/linux/xattr/pseudo_wrappers.c +++ b/ports/linux/xattr/pseudo_wrappers.c @@ -134,7 +134,7 @@ static ssize_t shared_getxattr(const char *path, int fd, const char *name, void pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n", path ? path : "<no path>", fd, name); pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name); - if (result->result != RESULT_SUCCEED) { + if (!result || result->result != RESULT_SUCCEED) { errno = ENOATTR; return -1; } @@ -197,7 +197,7 @@ static int shared_setxattr(const char *path, int fd, const char *name, const voi mode |= get_special_bits(path, fd); pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n", mode, extra); - buf.st_mode = mode; + /* we want to actually issue a corresponding chmod, * as well, or else the file ends up 0600 on the * host. Using the slightly-less-efficient wrap_chmod @@ -254,7 +254,7 @@ static int shared_setxattr(const char *path, int fd, const char *name, const voi static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size) { RC_AND_BUF pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf); - if (result->result != RESULT_SUCCEED) { + if (!result || result->result != RESULT_SUCCEED) { pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n"); errno = ENOATTR; return -1; @@ -276,7 +276,7 @@ static int shared_removexattr(const char *path, int fd, const char *name) { RC_AND_BUF pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name); - if (result->result != RESULT_SUCCEED) { + if (!result || result->result != RESULT_SUCCEED) { /* docs say ENOATTR, but I don't have one */ errno = ENOENT; return -1; diff --git a/ports/linux/xattr/wrapfuncs.in b/ports/linux/xattr/wrapfuncs.in index c37f78a..09eba23 100644 --- a/ports/linux/xattr/wrapfuncs.in +++ b/ports/linux/xattr/wrapfuncs.in @@ -1,12 +1,12 @@ -ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0 */ -ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */ -ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size); -int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0 */ -int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW */ -int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags); -ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0 */ -ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */ -ssize_t flistxattr(int filedes, char *list, size_t size); -int removexattr(const char *path, const char *name); /* flags=0 */ -int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW */ -int fremovexattr(int filedes, const char *name); +ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +ssize_t flistxattr(int filedes, char *list, size_t size); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int removexattr(const char *path, const char *name); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ +int fremovexattr(int filedes, const char *name); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */ diff --git a/ports/unix/guts/access.c b/ports/unix/guts/access.c index 1cc8d58..4725f49 100644 --- a/ports/unix/guts/access.c +++ b/ports/unix/guts/access.c @@ -21,7 +21,7 @@ if (buf.st_mode & 0111) { return 0; } else { - errno = EPERM; + errno = EACCES; return -1; } } else { diff --git a/ports/unix/guts/faccessat.c b/ports/unix/guts/faccessat.c new file mode 100644 index 0000000..02515ee --- /dev/null +++ b/ports/unix/guts/faccessat.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Linux Foundation; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static int + * wrap_fsaccessat(int dirfd, const char *path, int mode, int flags) { + * int rc = -1; + */ + rc = wrap_faccessat2(dirfd, path, mode, flags); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/faccessat2.c b/ports/unix/guts/faccessat2.c new file mode 100644 index 0000000..1283cc6 --- /dev/null +++ b/ports/unix/guts/faccessat2.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, Linux Foundation; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static int + * wrap_fsaccessat2(int dirfd, const char *path, int mode, int flags) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + if (flags & AT_SYMLINK_NOFOLLOW) { + rc = base_lstat(path, &buf); + } else { + rc = base_stat(path, &buf); + } +#else + rc = base_fstatat(dirfd, path, &buf, flags & AT_SYMLINK_NOFOLLOW); +#endif + if (rc == -1) + return rc; + + /* note: no attempt to handle the case where user isn't + * root. + */ + + if (mode & X_OK) { + if (buf.st_mode & 0111) { + return 0; + } else { + errno = EACCES; + return -1; + } + } else { + return 0; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchmodat.c b/ports/unix/guts/fchmodat.c index 55dbd35..5a31151 100644 --- a/ports/unix/guts/fchmodat.c +++ b/ports/unix/guts/fchmodat.c @@ -11,16 +11,16 @@ PSEUDO_STATBUF buf; int save_errno = errno; - if (flags & AT_SYMLINK_NOFOLLOW) { - errno = ENOTSUP; - return -1; - } #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS if (dirfd != AT_FDCWD) { errno = ENOSYS; return -1; } - rc = base_stat(path, &buf); + if (flags & AT_SYMLINK_NOFOLLOW) { + rc = base_lstat(path, &buf); + } else { + rc = base_stat(path, &buf); + } #else rc = base_fstatat(dirfd, path, &buf, flags); #endif @@ -28,9 +28,8 @@ return rc; } if (S_ISLNK(buf.st_mode)) { - /* we don't really support chmod of a symlink */ - errno = ENOSYS; - return -1; + /* according to docs, "chmod on a symbolic link always succeeds and has no effect" */ + return 0; } save_errno = errno; diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c index 381f9d0..7d8dff4 100644 --- a/ports/unix/guts/linkat.c +++ b/ports/unix/guts/linkat.c @@ -116,6 +116,7 @@ * if the thing linked is a symlink. */ pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf); + pseudo_client_linked_paths(oldpath, newpath); errno = save_errno; diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c index 085d2cb..8d8118b 100644 --- a/ports/unix/guts/realpath.c +++ b/ports/unix/guts/realpath.c @@ -14,7 +14,14 @@ errno = ENAMETOOLONG; return NULL; } - if ((len = strlen(rname)) >= pseudo_sys_path_max()) { + len = strlen(rname); + char *ep = rname + len - 1; + while (ep > rname && *ep == '/') { + --len; + *(ep--) = '\0'; + } + + if (len >= pseudo_sys_path_max()) { errno = ENAMETOOLONG; return NULL; } diff --git a/ports/unix/guts/rename.c b/ports/unix/guts/rename.c index 5073c71..80bbf41 100644 --- a/ports/unix/guts/rename.c +++ b/ports/unix/guts/rename.c @@ -13,7 +13,8 @@ int oldrc, newrc; int save_errno; int old_db_entry = 0; - int may_unlinked = 0; + int may_unlink_new = 0; + int may_unlink_old = 0; pseudo_debug(PDBGF_OP, "rename: %s->%s\n", oldpath ? oldpath : "<nil>", @@ -29,41 +30,51 @@ newrc = base_lstat(newpath, &newbuf); oldrc = base_lstat(oldpath, &oldbuf); + /* nothing to do for a "rename" of a link to itself */ + if (newrc != -1 && oldrc != -1 && + newbuf.st_dev == oldbuf.st_dev && + newbuf.st_ino == oldbuf.st_ino) { + pseudo_debug(PDBGF_OP, "rename: paths are the same\n"); + return real_rename(oldpath, newpath); + } + errno = save_errno; /* newpath must be removed. */ /* as with unlink, we have to mark that the file may get deleted */ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, newpath, newrc ? NULL : &newbuf); if (msg && msg->result == RESULT_SUCCEED) - may_unlinked = 1; + may_unlink_new = 1; + /* oldpath is also likely to disappear. Something could call stat() after + real_rename so we need to mark as MAY_UNLINK too */ + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, oldpath, oldrc ? NULL : &oldbuf); + if (msg && msg->result == RESULT_SUCCEED) + may_unlink_old = 1; + msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, oldrc ? NULL : &oldbuf); if (msg && msg->result == RESULT_SUCCEED) old_db_entry = 1; rc = real_rename(oldpath, newpath); save_errno = errno; - if (may_unlinked) { - if (rc == -1) { - /* since we failed, that wasn't really unlinked -- put - * it back. - */ - pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, newpath, &newbuf); - } else { - /* confirm that the file was removed */ - pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, newpath, &newbuf); - } - } + if (rc == -1) { + /* since we failed, that wasn't really unlinked -- put + * it back. + */ + if (may_unlink_new) + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, newpath, &newbuf); + if (may_unlink_old) + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, oldpath, &oldbuf); /* and we're done. */ errno = save_errno; return rc; } + + /* confirm that the file was removed */ + if (may_unlink_new) + pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, newpath, &newbuf); + /* OP_DID_UNLINK for oldpath is handled by the server */ save_errno = errno; - /* nothing to do for a "rename" of a link to itself */ - if (newrc != -1 && oldrc != -1 && - newbuf.st_dev == oldbuf.st_dev && - newbuf.st_ino == oldbuf.st_ino) { - return rc; - } /* rename(3) is not mv(1). rename(file, dir) fails; you must provide * the corrected path yourself. You can rename over a directory only diff --git a/ports/unix/guts/renameat.c b/ports/unix/guts/renameat.c index 735a60a..5ac63f9 100644 --- a/ports/unix/guts/renameat.c +++ b/ports/unix/guts/renameat.c @@ -13,7 +13,8 @@ int oldrc, newrc; int save_errno; int old_db_entry = 0; - int may_unlinked = 0; + int may_unlink_new = 0; + int may_unlink_old = 0; pseudo_debug(PDBGF_FILE, "renameat: %d,%s->%d,%s\n", olddirfd, oldpath ? oldpath : "<nil>", @@ -41,42 +42,51 @@ newrc = base_fstatat(newdirfd, newpath, &newbuf, AT_SYMLINK_NOFOLLOW); #endif + /* nothing to do for a "rename" of a link to itself */ + if (newrc != -1 && oldrc != -1 && + newbuf.st_dev == oldbuf.st_dev && + newbuf.st_ino == oldbuf.st_ino) { + pseudo_debug(PDBGF_OP, "renameat: paths are the same\n"); + return real_renameat(olddirfd, oldpath, newdirfd, newpath); + } + errno = save_errno; /* newpath must be removed. */ /* as with unlink, we have to mark that the file may get deleted */ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, newdirfd, newpath, newrc ? NULL : &newbuf); if (msg && msg->result == RESULT_SUCCEED) - may_unlinked = 1; + may_unlink_new = 1; + /* oldpath is also likely to disappear. Something could call stat() after + real_rename so we need to mark as MAY_UNLINK too */ + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, olddirfd, oldpath, oldrc ? NULL : &oldbuf); + if (msg && msg->result == RESULT_SUCCEED) + may_unlink_old = 1; + msg = pseudo_client_op(OP_STAT, 0, -1, olddirfd, oldpath, oldrc ? NULL : &oldbuf); if (msg && msg->result == RESULT_SUCCEED) old_db_entry = 1; rc = real_renameat(olddirfd, oldpath, newdirfd, newpath); save_errno = errno; - if (may_unlinked) { - if (rc == -1) { - /* since we failed, that wasn't really unlinked -- put - * it back. - */ - pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, newdirfd, newpath, &newbuf); - } else { - /* confirm that the file was removed */ - pseudo_client_op(OP_DID_UNLINK, 0, -1, newdirfd, newpath, &newbuf); - } - } if (rc == -1) { + /* since we failed, that wasn't really unlinked -- put + * it back. + */ + if (may_unlink_new) + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, newdirfd, newpath, &newbuf); + if (may_unlink_old) + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, olddirfd, oldpath, &oldbuf); /* and we're done. */ errno = save_errno; return rc; } + + /* confirm that the file was removed */ + if (may_unlink_new) + pseudo_client_op(OP_DID_UNLINK, 0, -1, newdirfd, newpath, &newbuf); + /* OP_DID_UNLINK for oldpath is handled by the server */ save_errno = errno; - /* nothing to do for a "rename" of a link to itself */ - if (newrc != -1 && oldrc != -1 && - newbuf.st_dev == oldbuf.st_dev && - newbuf.st_ino == oldbuf.st_ino) { - return rc; - } /* rename(3) is not mv(1). rename(file, dir) fails; you must provide * the corrected path yourself. You can rename over a directory only diff --git a/ports/unix/subports b/ports/unix/subports index e41b036..bd5a2f6 100755 --- a/ports/unix/subports +++ b/ports/unix/subports @@ -1,11 +1,13 @@ #!/bin/sh cat > dummy.c <<EOF +#define _GNU_SOURCE #include <unistd.h> int main(void) { syncfs(0); return 0; } EOF + if ${CC} -o dummy dummy.c > /dev/null 2>&1; then echo "unix/syncfs" fi diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in index 3910fae..7724fc7 100644 --- a/ports/unix/wrapfuncs.in +++ b/ports/unix/wrapfuncs.in @@ -1,15 +1,17 @@ int creat(const char *path, mode_t mode); char *getcwd(char *buf, size_t size); char *getwd(char *buf); -int close(int fd); +int close(int fd); /* noignore_path=1 */ int fchmod(int fd, mode_t mode); int fchown(int fd, uid_t owner, gid_t group); int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */ -int dup2(int oldfd, int newfd); -int dup(int fd); -int chdir(const char *path); -int fchdir(int dirfd); +int dup2(int oldfd, int newfd); /* noignore_path=1 */ +int dup(int fd); /* noignore_path=1 */ +int chdir(const char *path); /* noignore_path=1 */ +int fchdir(int dirfd); /* noignore_path=1 */ int access(const char *path, int mode); +int faccessat(int dirfd, const char *path, int mode, int flags); +int faccessat2(int dirfd, const char *path, int mode, int flags); FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */ int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd); int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag); @@ -20,18 +22,18 @@ char *mktemp(char *template); long pathconf(const char *path, int name); char *realpath(const char *name, char *resolved_name); /* version="GLIBC_2.3" */ int remove(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */ -DIR *opendir(const char *path); -int closedir(DIR *dirp); +DIR *opendir(const char *path); /* noignore_path=1 */ +int closedir(DIR *dirp); /* noignore_path=1 */ char *tempnam(const char *template, const char *pfx); char *tmpnam(char *s); int truncate(const char *path, off_t length); int utime(const char *path, const struct utimbuf *buf); int utimes(const char *path, const struct timeval *times); # needed because libc stdio does horrible things with inline asm syscalls -FILE *fopen(const char *path, const char *mode); -int fclose(FILE *fp); -FILE *freopen(const char *path, const char *mode, FILE *stream); -int chroot(const char *path); +FILE *fopen(const char *path, const char *mode); /* noignore_path=1 */ +int fclose(FILE *fp); /* noignore_path=1 */ +FILE *freopen(const char *path, const char *mode, FILE *stream); /* noignore_path=1 */ +int chroot(const char *path); /* noignore_path=1 */ int acct(const char *path); int chmod(const char *path, mode_t mode); int chown(const char *path, uid_t owner, gid_t group); @@ -295,7 +295,7 @@ options increase the debugging level. .TP 8 .BI \-x\ (debug) -Set specific deugging flags (the +Set specific debugging flags (the .I pseudo utility's help message lists them). This is equivalent to the string form of the @@ -245,10 +245,12 @@ main(int argc, char *argv[]) { /* Options are processed, preserve them... */ pseudo_set_value("PSEUDO_OPTS", opts); - if (!pseudo_get_prefix(argv[0])) { + s = pseudo_get_prefix(argv[0]); + if (!s) { pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n"); exit(PSEUDO_EXIT_PSEUDO_PREFIX); } + free(s); /* move database */ if (opt_m || opt_M) { @@ -680,31 +682,30 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **respon } break; default: - if (strcmp(msg->path, path_by_ino)) { + /* Ignore NAMELESS FILE entries since those could be created by other threads on new files */ + if (strcmp(msg->path, path_by_ino) && strcmp(path_by_ino, "NAMELESS FILE")) { mismatch = 1; } break; } if (mismatch) { - /* a mismatch, but we were planning to delete - * the file, so it must have gotten deleted - * already. - */ if (by_ino.deleting != 0) { - pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n", + /* a mismatch, likely a race but we were planning to + * delete the file, or are in the middle of a rename + * so continue, need the old entry for a rename. + */ + pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion.\n", msg->path); - pdb_did_unlink_file(path_by_ino, &by_ino, by_ino.deleting); } else { - int flags = 0; - if (msg->nlink > 1) { - flags = PDBGF_FILE | PDBGF_VERBOSE; - } - pseudo_debug(flags, "path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n", + pseudo_diag("path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n", msg->nlink, msg->nlink == 1 ? "" : "s", (unsigned long long) msg_header.ino, path_by_ino ? path_by_ino : "no path", msg->path); + found_ino = 0; + msg->result = RESULT_ABORT; + goto op_exit; } } } else { @@ -1024,6 +1025,24 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **respon break; } + switch (msg->op) { + case OP_FCHOWN: /* FALLTHROUGH */ + case OP_FCHMOD: /* FALLTHROUGH */ + case OP_FSTAT: + if (!found_path && !found_ino && (msg->nlink == 0)) { + /* If nlink is 0 for an fchown/fchmod/fstat, we probably have an fd which is + * unlinked and we don't want to do inode/path matching against it. Marking it + * as may unlink gives the right hints in the database to ensure we + * handle correctly whilst maintaining the permissions whilst the + * file exists for the fd. */ + pdb_may_unlink_file(msg, msg->client); + } + break; + default: + break; + } + +op_exit: /* in the case of an exact match, we just used the pointer * rather than allocating space. */ @@ -1087,9 +1106,15 @@ pseudo_db_check(int fix) { int fixup_needed = 0; pseudo_debug(PDBGF_DB, "Checking <%s>\n", m->path); if (lstat(m->path, &buf)) { - errors = EXIT_FAILURE; - pseudo_diag("can't stat <%s>\n", m->path); - continue; + if (!fix) { + pseudo_diag("can't stat <%s>\n", m->path); + errors = EXIT_FAILURE; + continue; + } else { + pseudo_debug(PDBGF_DB, "can't stat <%s>\n", m->path); + fixup_needed = 2; + goto do_fixup; + } } /* can't check for device type mismatches, uid/gid, or * permissions, because those are the very things we @@ -1125,6 +1150,7 @@ pseudo_db_check(int fix) { S_ISDIR(m->mode)); fixup_needed = 2; } + do_fixup: if (fixup_needed) { /* in fixup mode, either delete (mismatches) or * correct (dev/ino). diff --git a/pseudo_client.c b/pseudo_client.c index 478e450..a03d6b1 100644 --- a/pseudo_client.c +++ b/pseudo_client.c @@ -70,6 +70,8 @@ int pseudo_umask = 022; static char **fd_paths = NULL; static int nfds = 0; +static char **linked_fd_paths = NULL; +static int linked_nfds = 0; static const char **passwd_paths = NULL; static int npasswd_paths = 0; #ifdef PSEUDO_PROFILING @@ -429,6 +431,7 @@ pseudo_profile_report(void) { void pseudo_init_client(void) { char *env; + int need_free = 0; pseudo_antimagic(); pseudo_new_pid(); @@ -448,9 +451,11 @@ pseudo_init_client(void) { * or it may have gone away, in which case we'd enable * pseudo (and cause it to reinit the defaults). */ + need_free = 0; env = getenv("PSEUDO_DISABLED"); if (!env) { env = pseudo_get_value("PSEUDO_DISABLED"); + need_free = 1; } if (env) { int actually_disabled = 1; @@ -485,15 +490,19 @@ pseudo_init_client(void) { } else { pseudo_set_value("PSEUDO_DISABLED", "0"); } + if (need_free) + free(env); /* ALLOW_FSYNC is here because some crazy hosts will otherwise * report incorrect values for st_size/st_blocks. I can sort of * understand st_blocks, but bogus values for st_size? Not cool, * dudes, not cool. */ + need_free = 0; env = getenv("PSEUDO_ALLOW_FSYNC"); if (!env) { env = pseudo_get_value("PSEUDO_ALLOW_FSYNC"); + need_free = 1; } else { pseudo_set_value("PSEUDO_ALLOW_FSYNC", env); } @@ -502,6 +511,8 @@ pseudo_init_client(void) { } else { pseudo_allow_fsync = 0; } + if (need_free) + free(env); /* in child processes, PSEUDO_UNLOAD may become set to * some truthy value, in which case we're being asked to @@ -822,6 +833,8 @@ pseudo_client_chroot(const char *path) { } memcpy(pseudo_chroot, path, pseudo_chroot_len + 1); pseudo_set_value("PSEUDO_CHROOT", pseudo_chroot); + /* Rebuild passwd paths since we've done a chroot */ + build_passwd_paths(); return 0; } @@ -833,7 +846,7 @@ pseudo_root_path(const char *func, int line, int dirfd, const char *path, int le pseudo_magic(); if (!rc) { pseudo_diag("couldn't allocate absolute path for '%s'.\n", - path); + path ? path : "null"); } pseudo_debug(PDBGF_CHROOT, "root_path [%s, %d]: '%s' from '%s'\n", func, line, @@ -889,32 +902,73 @@ fd_path(int fd) { } static void -pseudo_client_path(int fd, const char *path) { +pseudo_client_path_set(int fd, const char *path, char ***patharray, int *len) { if (fd < 0) return; - if (fd >= nfds) { + if (fd >= *len) { int i; pseudo_debug(PDBGF_CLIENT, "expanding fds from %d to %d\n", - nfds, fd + 1); - fd_paths = realloc(fd_paths, (fd + 1) * sizeof(char *)); - for (i = nfds; i < fd + 1; ++i) - fd_paths[i] = 0; - nfds = fd + 1; + *len, fd + 1); + (*patharray) = realloc((*patharray), (fd + 1) * sizeof(char *)); + for (i = *len; i < fd + 1; ++i) + (*patharray)[i] = 0; + *len = fd + 1; } else { - if (fd_paths[fd]) { + if ((*patharray)[fd]) { pseudo_debug(PDBGF_CLIENT, "reopening fd %d [%s] -- didn't see close\n", - fd, fd_paths[fd]); + fd, (*patharray)[fd]); } /* yes, it is safe to free null pointers. yay for C89 */ - free(fd_paths[fd]); - fd_paths[fd] = 0; + free((*patharray)[fd]); + (*patharray)[fd] = 0; } if (path) { - fd_paths[fd] = strdup(path); + (*patharray)[fd] = strdup(path); + } +} + +static void +pseudo_client_path(int fd, const char *path) { + pseudo_client_path_set(fd, path, &fd_paths, &nfds); +} + +void +pseudo_client_linked_paths(const char *oldpath, const char *newpath) { + int fd; + for (fd = 3; fd < nfds; ++fd) { + if (fd_paths[fd] && !strcmp(oldpath, fd_paths[fd])) { + pseudo_client_path_set(fd, newpath, &linked_fd_paths, &linked_nfds); + } + } +} + +static void +pseudo_client_rename_path(const char *oldpath, const char *newpath) { + int fd; + for (fd = 3; fd < nfds; ++fd) { + if (fd_paths[fd] && !strcmp(oldpath, fd_paths[fd])) { + pseudo_client_path(fd, newpath); + } + } + for (fd = 0; fd < linked_nfds; ++fd) { + if (linked_fd_paths[fd] && fd_paths[fd] && !strcmp(oldpath, linked_fd_paths[fd])) { + pseudo_client_path_set(fd, newpath, &linked_fd_paths, &linked_nfds); + } + } +} + +static void +pseudo_client_unlinked_path(const char *path) { + int fd; + for (fd = 0; fd < linked_nfds; ++fd) { + if (linked_fd_paths[fd] && fd_paths[fd] && !strcmp(path, fd_paths[fd])) { + pseudo_client_path(fd, linked_fd_paths[fd]); + } } } + static void pseudo_client_close(int fd) { if (fd < 0 || fd >= nfds) @@ -922,6 +976,28 @@ pseudo_client_close(int fd) { free(fd_paths[fd]); fd_paths[fd] = 0; + + if (fd < linked_nfds) { + free(linked_fd_paths[fd]); + linked_fd_paths[fd] = 0; + } +} + +static void +pseudo_client_closefrom(int fd) { + int i; + if (fd < 0 || fd >= nfds) + return; + + for (i = fd; i < nfds; ++i) { + free(fd_paths[i]); + fd_paths[i] = 0; + + if (i < linked_nfds) { + free(linked_fd_paths[i]); + linked_fd_paths[i] = 0; + } + } } /* spawn server */ @@ -1271,7 +1347,7 @@ pseudo_client_setup(void) { } } -#define PSEUDO_RETRIES 20 +#define PSEUDO_RETRIES 250 static pseudo_msg_t * pseudo_client_request(pseudo_msg_t *msg, size_t len, const char *path) { pseudo_msg_t *response = 0; @@ -1436,8 +1512,12 @@ base_path(int dirfd, const char *path, int leave_last) { if (!path) return NULL; - if (!*path) + + if (!*path) { + if (dirfd != -1 && dirfd != AT_FDCWD) + return fd_path(dirfd); return ""; + } if (path[0] != '/') { if (dirfd != -1 && dirfd != AT_FDCWD) { @@ -1482,10 +1562,52 @@ base_path(int dirfd, const char *path, int leave_last) { return newpath; } +int pseudo_client_ignore_fd(int fd) { + if (fd >= 0 && fd <= nfds) + return pseudo_client_ignore_path(fd_path(fd)); + return 0; +} + +int pseudo_client_ignore_path_chroot(const char *path, int ignore_chroot) { + char *env; + + if (!path) + return 0; + + if (ignore_chroot && pseudo_chroot && strncmp(path, pseudo_chroot, pseudo_chroot_len) == 0) + return 0; + + env = pseudo_get_value("PSEUDO_IGNORE_PATHS"); + if (!env) + return 0; + + int ret = 0; + char *p = env; + while (p) { + char *next = strchr(p, ','); + if (next) + *next++ = '\0'; + if (*p && !strncmp(path, p, strlen(p))) { + pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "ignoring path: '%s'\n", path); + ret = 1; + break; + } + p = next; + } + free(env); + + return ret; +} + +int pseudo_client_ignore_path(const char *path) { + return pseudo_client_ignore_path_chroot(path, 1); +} + pseudo_msg_t * pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...) { pseudo_msg_t *result = 0; - pseudo_msg_t msg = { .type = PSEUDO_MSG_OP }; + static pseudo_msg_t msg; + msg = (pseudo_msg_t) { .type = PSEUDO_MSG_OP }; size_t pathlen = -1; int do_request = 0; char *path_extra_1 = 0; @@ -1495,6 +1617,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path static char *alloced_path = 0; static size_t alloced_len = 0; int strip_slash; + int startfd, i; #ifdef PSEUDO_PROFILING struct timeval tv1_op, tv2_op; @@ -1522,6 +1645,25 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path } } + if (op != OP_CHROOT && op != OP_CHDIR && op != OP_CLOSE && op != OP_CLOSEFROM && op != OP_DUP + && pseudo_client_ignore_path_chroot(path, 0)) { + if (op == OP_OPEN) { + /* Sanitise the path to have no trailing slash as this is convention in the database */ + char *stripped_path; + pathlen = strlen(path); + if (pathlen > 2 && (path[pathlen - 1]) == '/') { + stripped_path = strndup(path, pathlen-1); + pseudo_client_path(fd, stripped_path); + free(stripped_path); + } else { + pseudo_client_path(fd, path); + } + } + /* reenable wrappers */ + pseudo_magic(); + return result; + } + #ifdef PSEUDO_XATTRDB if (buf) { struct stat64 bufcopy = *buf; @@ -1772,9 +1914,36 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path break; case OP_OPEN: pseudo_client_path(fd, path); - case OP_EXEC: /* fallthrough */ + /* fallthrough */ + case OP_EXEC: do_request = pseudo_client_logging; break; + case OP_CLOSEFROM: + /* no request needed */ + startfd = fd; + if (pseudo_util_debug_fd > startfd) + startfd = pseudo_util_debug_fd + 1; + if (pseudo_localstate_dir_fd > startfd) + startfd = pseudo_localstate_dir_fd + 1; + if (pseudo_pwd_fd > startfd) + startfd = pseudo_pwd_fd + 1; + if (pseudo_grp_fd > startfd) + startfd = pseudo_grp_fd + 1; + if (connect_fd > startfd) + startfd = connect_fd + 1; + for (i = fd; i < startfd; ++i) { + if (i == pseudo_util_debug_fd || i == pseudo_localstate_dir_fd || i == pseudo_pwd_fd || + i == pseudo_grp_fd || i == connect_fd) + continue; + pseudo_client_close(i); + close(i); + } + pseudo_client_closefrom(startfd); + /* tell the caller to close from startfd instead of fd */ + result = &msg; + msg.fd = startfd; + do_request = 0; + break; case OP_CLOSE: /* no request needed */ if (fd >= 0) { @@ -1813,6 +1982,12 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path dirfd); pseudo_client_path(dirfd, fd_path(fd)); break; + case OP_UNLINK: + case OP_DID_UNLINK: + if (path) + pseudo_client_unlinked_path(path); + do_request = 1; + break; /* operations for which we should use the magic uid/gid */ case OP_CHMOD: case OP_CREAT: @@ -1833,10 +2008,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path case OP_FCHOWN: case OP_FSTAT: case OP_LINK: - case OP_RENAME: case OP_STAT: - case OP_UNLINK: - case OP_DID_UNLINK: case OP_CANCEL_UNLINK: case OP_MAY_UNLINK: case OP_GET_XATTR: @@ -1845,12 +2017,16 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path case OP_REMOVE_XATTR: do_request = 1; break; + case OP_RENAME: + pseudo_client_rename_path(path_extra_1, path); + do_request = 1; + break; default: pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op)); break; } /* result can only be set when PSEUDO_XATTRDB resulted in a - * successful store to or read from the local database. + * successful store to or read from the local database or for OP_CLOSEFROM. */ if (do_request && !result) { #ifdef PSEUDO_PROFILING @@ -1876,6 +2052,12 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path #endif if (result) { pseudo_debug(PDBGF_OP, "(%d) %s", getpid(), pseudo_res_name(result->result)); + if (result->result == RESULT_ABORT) { + char *local_state_dir = pseudo_get_value("PSEUDO_LOCALSTATEDIR"); + pseudo_diag("abort()ing pseudo client by server request. See https://wiki.yoctoproject.org/wiki/Pseudo_Abort for more details on this.\n" + "Check logfile: %s/%s\n", local_state_dir, PSEUDO_LOGFILE); + abort(); + } if (op == OP_STAT || op == OP_FSTAT) { pseudo_debug(PDBGF_OP, " mode 0%o uid %d:%d", (int) result->mode, diff --git a/pseudo_client.h b/pseudo_client.h index 457b095..d7944ce 100644 --- a/pseudo_client.h +++ b/pseudo_client.h @@ -7,6 +7,9 @@ * */ extern pseudo_msg_t *pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...); +extern int pseudo_client_ignore_path(const char *path); +extern int pseudo_client_ignore_fd(int fd); +extern void pseudo_client_linked_paths(const char *oldpath, const char *newpath); #if PSEUDO_STATBUF_64 #define base_lstat real_lstat64 #define base_fstat real_fstat64 diff --git a/pseudo_db.c b/pseudo_db.c index 92e4f50..14bafcb 100644 --- a/pseudo_db.c +++ b/pseudo_db.c @@ -158,11 +158,24 @@ static struct sql_index { static char *file_pragmas[] = { "PRAGMA legacy_file_format = OFF;", - "PRAGMA journal_mode = OFF;", +#ifdef USE_MEMORY_DB /* the default page size produces painfully bad behavior * for memory databases with some versions of sqlite. */ "PRAGMA page_size = 8192;", + "PRAGMA journal_mode = OFF;", +#else + /* Use WAL mode when using the on-disk database. If user care about + * performance, they can use the in-memory database, but if they care + * more about resilience, they can disable it and WAL mode will prevent + * corruption of the on-disk database (for a slight performance + * penalty). Note that the database still keeps synchronous to OFF, + * meaning its resilient to the pseudo process crashing or being killed + * unexpectedly, but not to the OS crashing and losing buffered disk + * state + */ + "PRAGMA journal_mode = WAL;", +#endif "PRAGMA locking_mode = EXCLUSIVE;", /* Setting this to NORMAL makes pseudo noticably slower * than fakeroot, but is perhaps more secure. However, @@ -386,7 +399,7 @@ signed_ino(ino_t ino) { #ifdef USE_MEMORY_DB -static void +void pdb_backup() { sqlite3_backup *pBackup; /* no point in doing this if we don't have a database to back up, @@ -2007,8 +2020,8 @@ int pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) { static sqlite3_stmt *update_exact, *update_sub; int rc; - char *sql_update_exact = "UPDATE files SET path = ? WHERE path = ?;"; - char *sql_update_sub = "UPDATE files SET path = replace(path, ?, ?) " + char *sql_update_exact = "UPDATE files SET path = ?, deleting = 0 WHERE path = ?;"; + char *sql_update_sub = "UPDATE files SET path = replace(path, ?, ?), deleting = 0 " "WHERE (path > (? || '/') AND path < (? || '0'));"; if (!file_db && get_dbs()) { diff --git a/pseudo_db.h b/pseudo_db.h index 5a4aa59..9d01232 100644 --- a/pseudo_db.h +++ b/pseudo_db.h @@ -26,6 +26,7 @@ typedef struct { char *program; } log_entry; +extern void pdb_backup(void); extern int pdb_maybe_backup(void); extern int pdb_cancel_unlink_file(pseudo_msg_t *msg); extern int pdb_did_unlink_file(char *path, pseudo_msg_t *msg, int deleting); diff --git a/pseudo_ipc.h b/pseudo_ipc.h index caeae5c..d945257 100644 --- a/pseudo_ipc.h +++ b/pseudo_ipc.h @@ -29,7 +29,7 @@ typedef struct { char path[]; } pseudo_msg_t; -enum { +typedef enum { PSA_EXEC = 1, PSA_WRITE = (PSA_EXEC << 1), PSA_READ = (PSA_WRITE << 1), diff --git a/pseudo_server.c b/pseudo_server.c index 898aab4..815c76b 100644 --- a/pseudo_server.c +++ b/pseudo_server.c @@ -568,6 +568,7 @@ serve_client(int i) { } in->pathlen = (s - response_path) + 1; /* exit quickly once clients go away, though */ + pdb_backup(); pseudo_server_timeout = 3; } else { in->type = PSEUDO_MSG_ACK; @@ -792,6 +793,7 @@ pseudo_server_loop(void) { struct sigaction eat_usr2 = { .sa_handler = set_do_list_clients }; + int hitmaxfiles; clients = malloc(16 * sizeof(*clients)); @@ -810,6 +812,7 @@ pseudo_server_loop(void) { active_clients = 1; max_clients = 16; highest_client = 0; + hitmaxfiles = 0; pseudo_debug(PDBGF_SERVER, "server loop started.\n"); if (listen_fd < 0) { @@ -868,10 +871,15 @@ pseudo_server_loop(void) { } else { serve_client(i); } + } else if (hitmaxfiles) { + /* Only close one per loop iteration in the interests of caution */ + close_client(i); + hitmaxfiles = 0; } if (die_forcefully) break; } + hitmaxfiles = 0; if (!die_forcefully && (FD_ISSET(clients[0].fd, &events) || FD_ISSET(clients[0].fd, &reads))) { @@ -893,6 +901,9 @@ pseudo_server_loop(void) { */ pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT; die_peacefully = 0; + } else if (errno == EMFILE) { + hitmaxfiles = 1; + pseudo_debug(PDBGF_SERVER, "Hit max open files, dropping a client.\n"); } } pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients); diff --git a/pseudo_util.c b/pseudo_util.c index c867ed6..b58036f 100644 --- a/pseudo_util.c +++ b/pseudo_util.c @@ -43,6 +43,7 @@ static struct pseudo_variables pseudo_env[] = { { "PSEUDO_BINDIR", 13, NULL }, { "PSEUDO_LIBDIR", 13, NULL }, { "PSEUDO_LOCALSTATEDIR", 20, NULL }, + { "PSEUDO_IGNORE_PATHS", 19, NULL }, { "PSEUDO_PASSWD", 13, NULL }, { "PSEUDO_CHROOT", 13, NULL }, { "PSEUDO_UIDS", 11, NULL }, @@ -158,7 +159,7 @@ pseudo_get_value(const char *key) { if (pseudo_util_initted == -1) pseudo_init_util(); - for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++) + for (i = 0; pseudo_env[i].key && strcmp(pseudo_env[i].key, key); i++) ; /* Check if the environment has it and we don't ... @@ -187,7 +188,7 @@ pseudo_set_value(const char *key, const char *value) { if (pseudo_util_initted == -1) pseudo_init_util(); - for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++) + for (i = 0; pseudo_env[i].key && strcmp(pseudo_env[i].key, key); i++) ; if (pseudo_env[i].key) { @@ -678,7 +679,7 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre if (!leave_this && is_dir) { int is_link = S_ISLNK(buf->st_mode); if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) { - pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath); + pseudo_debug(PDBGF_PATH, "link recursion too deep, not expanding path '%s'.\n", newpath); is_link = 0; } if (is_link) { @@ -688,14 +689,15 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre linklen = readlink(newpath, linkbuf, pseudo_path_max()); if (linklen == -1) { - pseudo_diag("uh-oh! '%s' seems to be a symlink, but I can't read it. Ignoring.", newpath); + pseudo_debug(PDBGF_PATH, "uh-oh! '%s' seems to be a symlink, but I can't read it. Ignoring.\n", newpath); + *pcurrent = current; return 0; } /* null-terminate buffer */ linkbuf[linklen] = '\0'; - /* absolute symlink means start over! */ + /* absolute symlink means go back to root */ if (*linkbuf == '/') { - current = newpath; + current = root; } else { /* point back at the end of the previous path... */ current -= (elen + 1); @@ -790,11 +792,9 @@ static char *pathbufs[PATHBUFS] = { 0 }; static int pathbuf = 0; /* Canonicalize path. "base", if present, is an already-canonicalized - * path of baselen characters, presumed not to end in a /. path is - * the new path to be canonicalized. The tricky part is that path may - * contain symlinks, which must be resolved. - * if "path" starts with a /, then it is an absolute path, and - * we ignore base. + * path of baselen characters, presumed not to end in a / unless it is + * just "/". path is the new path to be canonicalized. The tricky part + * is that path may contain symlinks, which must be resolved. */ char * pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t baselen, size_t *lenp, int leave_last) { @@ -808,13 +808,28 @@ pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t basel pseudo_diag("can't fix empty path.\n"); return 0; } + if (baselen == 1) { + baselen = 0; + } + if (rootlen == 1) { + rootlen = 0; + } newpathlen = pseudo_path_max(); + pathlen = strlen(path); + /* Crazy shell code (e.g. libtool) can pass in a command pipeline as a path which exceeds the max path + * length the system can support (6000+ chars). This will fail in libc or the syscall but if we don't + * do something here, we'd segfault before it can do that. Leave path unchanged and let libc deal + * with it. + */ + if ((pathlen + baselen) >= newpathlen) { + return path; + } if (!pathbufs[pathbuf]) { pathbufs[pathbuf] = malloc(newpathlen); } newpath = pathbufs[pathbuf]; pathbuf = (pathbuf + 1) % PATHBUFS; - pathlen = strlen(path); + /* a trailing slash has special meaning, but processing * trailing slashes is expensive. */ @@ -966,6 +981,7 @@ pseudo_setupenv() { } snprintf(newenv, len, "%s:%s64", libdir_path, libdir_path); SETENV(PRELINK_PATH, newenv, 1); + free(newenv); } else if (!strstr(ld_library_path, libdir_path)) { size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1; char *newenv = malloc(len); @@ -974,6 +990,7 @@ pseudo_setupenv() { } snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path); SETENV(PRELINK_PATH, newenv, 1); + free(newenv); } else { /* nothing to do, ld_library_path exists and contains * our preferred path */ @@ -1594,7 +1611,7 @@ pseudo_logfile(char *filename, char *defname, int prefer_fd) { } free(filename); } - fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT, 0644); + fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644); if (fd == -1) { pseudo_diag("help: can't open log file %s: %s\n", pseudo_path, strerror(errno)); } else { diff --git a/pseudo_wrappers.c b/pseudo_wrappers.c index 99aabff..9ae1200 100644 --- a/pseudo_wrappers.c +++ b/pseudo_wrappers.c @@ -140,7 +140,7 @@ pseudo_init_one_wrapper(pseudo_function *func) { #endif f = dlsym(RTLD_NEXT, func->name); if (f) { - *func->real = f; + *func->real = (void (*)(void)) f; } /* it turns out that in some cases, we get apparently-harmless * errors if a function is missing, and that printing output diff --git a/pseudolog.c b/pseudolog.c index 1101f28..ad04753 100644 --- a/pseudolog.c +++ b/pseudolog.c @@ -8,7 +8,8 @@ */ /* We need _XOPEN_SOURCE for strptime(), but if we define that, * we then don't get S_IFSOCK... _GNU_SOURCE turns on everything. */ -#define _GNU_SOURCE +#define _DEFAULT_SOURCE +#define _XOPEN_SOURCE #include <ctype.h> #include <limits.h> @@ -374,7 +375,7 @@ plog_trait(int opt, char *string) { pseudo_diag("invalid empty string for -%c\n", opt); return 0; } - new_trait = calloc(sizeof(*new_trait), 1); + new_trait = calloc(1, sizeof(*new_trait)); if (!new_trait) { pseudo_diag("Couldn't allocate requested trait (for -%c %s)\n", opt, string ? string : "<nil>"); diff --git a/templates/wrapfuncs.c b/templates/wrapfuncs.c index 3859183..93bb671 100644 --- a/templates/wrapfuncs.c +++ b/templates/wrapfuncs.c @@ -60,9 +60,15 @@ ${maybe_async_skip} ${rc_assign} (*real_${name})(${call_args}); } else { ${fix_paths} - /* exec*() use this to restore the sig mask */ - pseudo_saved_sigmask = saved; - ${rc_assign} wrap_$name(${call_args}); + if (${ignore_paths}) { + /* call the real syscall */ + pseudo_debug(PDBGF_SYSCALL, "${name} ignored path, calling real syscall.\n"); + ${rc_assign} (*real_${name})(${call_args}); + } else { + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + ${rc_assign} wrap_$name(${call_args}); + } } ${variadic_end} save_errno = errno; diff --git a/templates/wrapper_table b/templates/wrapper_table index bb30530..498ca81 100644 --- a/templates/wrapper_table +++ b/templates/wrapper_table @@ -6,8 +6,8 @@ * script if you want to modify this. */ typedef struct { char *name; /* the name */ - int (**real)(void); /* the underlying syscall */ - int (*wrapper)(void); /* the wrapper from guts/name.c */ + void (**real)(void); /* the underlying syscall */ + void (*wrapper)(void); /* the wrapper from guts/name.c */ char *version; /* the version, if we know and care */ } pseudo_function; @@ -15,8 +15,8 @@ static pseudo_function pseudo_functions[] = { @body { /* ${comment}; */ "${name}${maybe_inode64}", - (int (**)(void)) &real_${name}, - (int (*)(void)) wrap_${name}, + (void (**)(void)) &real_${name}, + (void (*)(void)) wrap_${name}, ${version} }, @footer diff --git a/test/test-acl.sh b/test/test-acl.sh new file mode 100755 index 0000000..fb7d5ec --- /dev/null +++ b/test/test-acl.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# + +# Return vals: 2 - Unable to run ACL commands, assertion failure +# 1 - Invalid return value +# 0 - Pass + +# NOTE: these test exclusively test setfacl -m + +set -u + +check_owner () { + local file="$1" + local expected="$2" + local msg="$3" + local actual=$(stat -c "%U" "$file") + if [ "$actual" != "$expected" ] + then + echo "$msg" "Fail, '$file' unexpected owner '$actual'" + exit 2 + fi +} + +check_group () { + local file="$1" + local expected="$2" + local msg="$3" + local actual=$(stat -c "%G" "$file") + if [ "$actual" != "$expected" ] + then + echo "$msg" "Fail, '$file' unexpected group '$actual'" + exit 2 + fi +} + +check_acl_contains () { + local file="$1" + local acl="$2" + local msg="$3" + IFS=',' read -ra acls <<< "$acl" + for pattern in "${acls[@]}"; do + result=$(getfacl -c "$file" | grep -o "^$pattern") + if [ "$result" != "$pattern" ] + then + echo "$msg" "Fail, did not find desired acl '$pattern' in '$file'" + exit 2 + fi + done +} + +check_acl_minimal () { + local file="$1" + local msg="${2:-''}" + local acls + acls=$(getfacl -c "${file}" | grep -v "::") + if [ -n "$acls" ] + then + echo "$msg" "Fail, '$file' unexpected getfacl result '$acls'" + exit 1 + fi +} + +test_modify_once () { + local file="$1" + local acl="$2" + local msg="${3:-''}" + # ensure that file is pristine + check_acl_minimal "$file" "$msg precondition:" + check_owner "$file" root "$msg precondition:" + check_group "$file" root "$msg precondition:" + if ! setfacl -m "$acl" "$file" + then + echo "$msg" "Fail, unable to call setfacl" + exit 2 + fi + check_acl_contains "$file" "$acl" "$msg: acl not set:" + check_owner "$file" root "$msg owner corrupted:" + check_group "$file" root "$msg group corrupted:" +} + + +trap "rm -rf testdir" EXIT +mkdir testdir || exit 1 + + +# user +touch testdir/f1 || exit 1 +mkdir testdir/d1 || exit 1 +# regular file +test_modify_once testdir/f1 "user:root:r" "$LINENO:" +# directory +test_modify_once testdir/d1 "user:root:r" "$LINENO:" +rm -rf testdir/f1 testdir/d1 + +#group +rm -rf testdir/f1 testdir/d1 +touch testdir/f1 || exit 1 +mkdir testdir/d1 || exit 1 +# regular file +test_modify_once testdir/f1 "group:root:r" "$LINENO:" +# directory +test_modify_once testdir/d1 "group:root:r" "$LINENO:" +rm -rf testdir/f1 testdir/d1 + +# multiple users +touch testdir/f1 || exit 1 +mkdir testdir/d1 || exit 1 +# regular file +test_modify_once testdir/f1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:" +# directory +test_modify_once testdir/d1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:" +rm -rf testdir/f1 testdir/d1 + + +# setfacl default acls +mkdir testdir/d1 || exit 1 +test_modify_once testdir/d1 "default:user:root:r,user:root:r" "$LINENO:" +rm -rf testdir/d1 + + +# multiple calls to setfacl -m on same file +touch testdir/f1 || exit 1 +mkdir testdir/d1 || exit 1 +check_owner testdir/f1 root "$LINENO: precondition:" +check_group testdir/f1 root "$LINENO: precondition:" +check_acl_minimal testdir/f1 "$LINENO: precondition:" + +acl1="user:root:r" +acl2="user:bin:rw" + +if ! setfacl -m "$acl1" testdir/f1 # first setfacl +then + echo "$LINENO:" "Fail, unable to call setfacl" + exit 2 +fi +check_acl_contains testdir/f1 "$acl1" "$LINENO: acl1 not set:" +check_owner testdir/f1 root "$LINENO: owner corrupted:" +check_group testdir/f1 root "$LINENO: group corrupted:" + +if ! setfacl -m "$acl2" testdir/f1 # second setfacl +then + echo "$LINENO:" "Fail, unable to call setfacl" + exit 2 +fi + +check_acl_contains testdir/f1 "$acl1" "$LINENO: acl1 not set:" +check_acl_contains testdir/f1 "$acl2" "$LINENO: acl2 not set:" +check_owner testdir/f1 root "$LINENO: owner corrupted:" +check_group testdir/f1 root "$LINENO: group corrupted:" +rm -rf testdir/f1 testdir/d1 + +# setfacl recursive +test_modify_recursive () { + local root_dir="$1" + local acl="$2" + local msg="${3:-''}" + + find "$root_dir" | while read -r file; do + check_owner "$file" root "$msg precondition:" + check_group "$file" root "$msg precondition:" + check_acl_minimal "$file" "$msg precondition:" + done + if ! setfacl -R -m "$acl" "$root_dir" + then + echo "$msg" "Fail, unable to call setfacl" + exit 2 + fi + find "$root_dir" | while read -r file; do + check_owner "$file" root "$msg owner corrupted:" + check_group "$file" root "$msg group corrupted:" + check_acl_contains "$file" "$acl" "$msg acl not set:" + done +} + +mkdir -p testdir/d1/d2 || exit 1 +touch testdir/d1/d2/f1 || exit 1 +test_modify_recursive testdir/d1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:" +rm -rf testdir/d1 + +mkdir -p testdir/d1/d2 || exit 1 +mkdir -p testdir/d1/d3 || exit 1 +test_modify_recursive testdir/d1 "default:user:root:rwx,user:root:r,group:root:r,user:bin:rw" "$LINENO:" +rm -rf testdir/d1 + +#echo "Passed." +exit 0 diff --git a/test/test-chroot-symlink.c b/test/test-chroot-symlink.c new file mode 100644 index 0000000..469cb49 --- /dev/null +++ b/test/test-chroot-symlink.c @@ -0,0 +1,28 @@ +/* + * Test that stat'ing absolute/relative symlinks in a chroot environment works + * SPDX-License-Identifier: LGPL-2.1-only + * + */ +#define _GNU_SOURCE + +#include <unistd.h> +#include <sys/stat.h> +#include <stdio.h> + +int main(int argc, char *argv[]) { + struct stat buf; + + if (argc != 2) { + perror("args"); + return 2; + } + if (chroot(argv[1]) == -1) { + perror("chroot"); + return 1; + } + if (stat("symlink", &buf) == -1) { + perror("stat symlink"); + return 1; + } + return 0; +} diff --git a/test/test-chroot-symlink.sh b/test/test-chroot-symlink.sh new file mode 100755 index 0000000..91092c1 --- /dev/null +++ b/test/test-chroot-symlink.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Test that stat'ing absolute/relative symlinks in a chroot environment works +# SPDX-License-Identifier: LGPL-2.1-only +# + +set -e + +touch symlink_target +trap "rm -f symlink_target symlink" 0 + +# Absolute symlink +ln -s /symlink_target symlink +./test/test-chroot-symlink `pwd` +rm symlink + +# Relative symlink +ln -s symlink_target symlink +./test/test-chroot-symlink `pwd` diff --git a/test/test-fcntl.c b/test/test-fcntl.c new file mode 100644 index 0000000..b593d50 --- /dev/null +++ b/test/test-fcntl.c @@ -0,0 +1,58 @@ +/* fcntl-linux.h doesn't define F_GETPIPE_SZ and F_SETPIPE_SZ without + * this */ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <string.h> + +int test_pipe_sz() +{ +#if defined(F_GETPIPE_SZ) && defined(F_SETPIPE_SZ) + int pipefd[2]; + + if (pipe(pipefd) < 0) { + perror("pipe"); + return 1; + } + + const int orig_size = fcntl(pipefd[0], F_GETPIPE_SZ); + if (orig_size < 0) { + perror("F_GETPIPE_SZ"); + return 1; + } + + const int new_size = orig_size * 2; + + if (fcntl(pipefd[0], F_SETPIPE_SZ, new_size) < 0) { + perror("F_SETPIPE_SZ"); + return 1; + } + + const int final_size = fcntl(pipefd[0], F_GETPIPE_SZ); + if (final_size < 0) { + perror("Second F_GETPIPE_SZ"); + return 1; + } + + if (final_size < new_size) { + fprintf(stderr, "Unexpected final pipe size: %d\n", final_size); + return 1; + } +#else + printf("Host too old for F_GETPIPE_SZ and F_SETPIPE_SZ tests\n"); +#endif + return 0; +} + +int main() +{ + int result = 0; + result += test_pipe_sz(); + return result; +} diff --git a/test/test-fcntl.sh b/test/test-fcntl.sh new file mode 100755 index 0000000..7112620 --- /dev/null +++ b/test/test-fcntl.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# +./test/test-fcntl diff --git a/test/test-fstat.c b/test/test-fstat.c new file mode 100644 index 0000000..78f7013 --- /dev/null +++ b/test/test-fstat.c @@ -0,0 +1,29 @@ +/* + * Test that stat'ing a file descriptor of a symlink does not dereference the symlink + * SPDX-License-Identifier: LGPL-2.1-only + * + */ +#define _GNU_SOURCE + +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdio.h> + +int main(void) { + struct stat buf; + int fd = open("symlink", O_NOFOLLOW | O_PATH); + if (fd == -1) { + perror("open symlink"); + return 1; + } + if (fstatat(fd, "", &buf, AT_EMPTY_PATH) == -1) { + perror("stat symlink"); + return 1; + } + if (S_ISLNK(buf.st_mode) != 1) { + fprintf(stderr, "path is not a symlink\n"); + return 1; + } + return 0; +} diff --git a/test/test-fstat.sh b/test/test-fstat.sh new file mode 100755 index 0000000..88eab8b --- /dev/null +++ b/test/test-fstat.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# + +ln -s . symlink +trap "rm symlink" 0 +./test/test-fstat diff --git a/test/test-openat.c b/test/test-openat.c new file mode 100644 index 0000000..df6655a --- /dev/null +++ b/test/test-openat.c @@ -0,0 +1,58 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <string.h> + +char *path_of(int fd) { + ssize_t len; + char proc[PATH_MAX], real[PATH_MAX]; + snprintf(proc, sizeof(proc), "/proc/self/fd/%d", fd); + len = readlink(proc, real, sizeof(real)); + real[len] = '\0'; + return strdup(real); +} + +/* +* Test that recusing up the tree with openat(fd, "../") handles slashes +* correctly and doesn't end up opening the same directory instead of going up a +* level. +*/ +int main () { + int fd, dir_fd; + struct stat st; + ino_t ino; + dev_t dev; + char *path; + + fd = openat(AT_FDCWD, ".", O_DIRECTORY, 0); + fstat(fd, &st); + ino = st.st_ino; + dev = st.st_dev; + + while (1) { + path = path_of(fd); + //puts(path); + + dir_fd = openat(fd, "../", O_DIRECTORY, 0); + fstat(dir_fd, &st); + if (st.st_ino == ino && st.st_dev == dev) { + if (strcmp(path, "/") == 0) { + //puts("Reached top of tree"); + return 0; + } else { + //puts("Recursion failed!"); + return 1; + } + } + + free (path); + ino = st.st_ino; + dev = st.st_dev; + fd = dir_fd; + } + return 0; +} diff --git a/test/test-openat.sh b/test/test-openat.sh new file mode 100755 index 0000000..0455586 --- /dev/null +++ b/test/test-openat.sh @@ -0,0 +1,7 @@ +#! /bin/sh + +# Test with and without paths being ignored. The bug was with paths being ignored. + +./test/test-openat + +PSEUDO_IGNORE_PATHS=/ ./test/test-openat diff --git a/test/test-relative-from-root.sh b/test/test-relative-from-root.sh new file mode 100755 index 0000000..e2c230e --- /dev/null +++ b/test/test-relative-from-root.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# pseudo had a bug that made it abort() when looking up a relative path from +# base "/", such as by openat(dirfd_of_root, "foo/bar") or when cwd is /. It +# tried to look up base+"/"+path = "//foo/bar", which is wrong. + +set -e + +touch f1 +relative_pwd=${PWD#/} + +cd / +cat "$relative_pwd/f1" + +rm "$relative_pwd/f1" diff --git a/test/test-rename-fstat.c b/test/test-rename-fstat.c new file mode 100644 index 0000000..fb47c05 --- /dev/null +++ b/test/test-rename-fstat.c @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-only + * + * Test we can rename a file whilst holding an open fd which we fstat after renaming + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +int main() +{ + struct stat buf; + int err; + int fd = open("test-rename-fstat1", O_RDONLY); + err = rename("test-rename-fstat1", "test-rename-fstat1"); + if (err) + return err; + return fstat(fd, &buf); +} diff --git a/test/test-rename-fstat.sh b/test/test-rename-fstat.sh new file mode 100755 index 0000000..4ac89b8 --- /dev/null +++ b/test/test-rename-fstat.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# + +rm -f test-rename-fstat1 test-rename-fstat2 +touch test-rename-fstat1 +# Will abort if it fails +./test/test-rename-fstat +ecode=$? +rm -f test-rename-fstat1 test-rename-fstat2 +exit $ecode diff --git a/test/test-statx.c b/test/test-statx.c new file mode 100644 index 0000000..06d86af --- /dev/null +++ b/test/test-statx.c @@ -0,0 +1,20 @@ +/* + * Test that passing NULL to a parameter marked as nonnull works correctly + * SPDX-License-Identifier: LGPL-2.1-only + * + */ +#define _GNU_SOURCE + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +// Passing a null pointer is the test scenario +#pragma GCC diagnostic ignored "-Wnonnull" + +int main(void) { + if (statx(0, NULL, 0, 0, NULL) != -1) { + return 1; + } + return 0; +} diff --git a/test/test-statx.sh b/test/test-statx.sh new file mode 100755 index 0000000..77d0302 --- /dev/null +++ b/test/test-statx.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# + +exec ./test/test-statx diff --git a/test/test-umask.sh b/test/test-umask.sh index e4e366b..e09fdbf 100755 --- a/test/test-umask.sh +++ b/test/test-umask.sh @@ -35,3 +35,4 @@ case $(mode b) in *) exit 1;; esac +rm a b |