mbox series

[pseudo,00/20] Consolidated pseudo patches

Message ID 1768520616-7289-1-git-send-email-mark.hatle@kernel.crashing.org
Headers show
Series Consolidated pseudo patches | expand

Message

Mark Hatle Jan. 15, 2026, 11:43 p.m. UTC
This is the full set of pending changes.  Many of which have already
been sent to the mailing list.  This set matches 'fray/master' branch
in the repository.

The new features in this set include:

* Test harness improvements (better display, more robust)
* Realpath POSIX fix from Gauthier HADERER
* Linux path traversal into /proc fixes
* General cleanup

This along with the other changes that have recently gone in should
get us to 1.9.3 or even 2.0.0.


Note: the test suite still shows there are problems with parallel
rename and symlink operations.  These are reported but we don't
have any sort of a fix in mind yet.

renameat2 is not implemented, this will require potentially extensive
changes to pseudo to allow for atomic exchange (rename).  It will
return ENOSYS until then.

openat2 is not implemented, a prototype version of this code was
created, but does not yet pass testing.  This also returns ENOSYS.

Gauthier HADERER (1):
  ports/unix/guts/realpath.c: realpath fails if the resolved path
    doesn't exist

Mark Hatle (19):
  test-syscall: Remove build warning
  test: Cleanup test output
  test/test-statx.sh: It should be a failure if pseudo prints an error
  test-realpath: Verify the realpath behavior matches glibc
  run_tests.sh: In verbose mode, include pseudo.log in output
  test/test-statx: Add uutils test case
  test/test-nftw: Avoid compile warnings
  test-tclsh-fork: Skip test if tclsh is not available
  test/test-proc-pipe.sh: Add test case for proc pipes
  pseudo_util.c: Skip realpath like expansion for /proc on Linux
  ports/unix/guts/realpath.c: Fix indents
  ports/linux/pseudo_wrappers.c: Reorder the syscall operations
  ports/linux/pseudo_wrappers.c: Call the wrappers where possible
  ports/linux: Add additional EFAULTS for Linux functions
  Update COPYRIGHT files
  makewrappers: improve error handling and robustness
  pseudo: code quality scan - resolved various potential issues
  configure: Minor code quality changes
  Makefile.in: Bump version to 1.9.3

 ChangeLog.txt                       |   4 +
 Makefile.in                         |   5 +-
 configure                           |  19 ++--
 guts/COPYRIGHT                      |   2 +
 makewrappers                        |  73 ++++++++-------
 ports/darwin/guts/COPYRIGHT         |   1 +
 ports/linux/guts/COPYRIGHT          |   2 +
 ports/linux/openat2/wrapfuncs.in    |   2 +-
 ports/linux/pseudo_wrappers.c       |  46 ++++++----
 ports/linux/wrapfuncs.in            |  22 ++---
 ports/linux/xattr/wrapfuncs.in      |  16 ++--
 ports/uids_generic/guts/COPYRIGHT   |   1 +
 ports/unix/guts/COPYRIGHT           |   2 +
 ports/unix/guts/fts_open.c          |   5 +-
 ports/unix/guts/nftw_wrapper_base.c |  11 +--
 ports/unix/guts/realpath.c          |  23 +++--
 pseudo.c                            |  61 ++++++++-----
 pseudo_client.c                     |   4 +
 pseudo_db.c                         |  10 +++
 pseudo_server.c                     |  14 +++
 pseudo_util.c                       | 132 +++++++++++++++++++++++-----
 run_tests.sh                        |  28 ++++--
 test/nftw-test-impl.c               |   2 +
 test/test-acl.sh                    |  16 ++++
 test/test-proc-pipe.sh              |  27 ++++++
 test/test-realpath.c                |  17 ++++
 test/test-realpath.sh               |  63 +++++++++++++
 test/test-statx.sh                  |  39 +++++++-
 test/test-syscall.c                 |   2 +-
 test/test-tclsh-fork.sh             |   5 ++
 test/test-xattr.sh                  |  17 ++++
 31 files changed, 525 insertions(+), 146 deletions(-)
 create mode 100755 test/test-proc-pipe.sh
 create mode 100644 test/test-realpath.c
 create mode 100755 test/test-realpath.sh

Comments

Mark Hatle Jan. 17, 2026, 4:08 p.m. UTC | #1
On 1/15/26 5:43 PM, Mark Hatle wrote:
> The seccomp wrap always takes effect when pseudo is running, this will
> prevent various behavior, even if pseudo is generally considered to be
> disabled, but in memory.
> 
> The openat2 and renameat2 however should only run if pseudo is enabled.
> 
> Signed-off-by: Mark Hatle <mark.hatle@kernel.crashing.org>
> ---
>   ports/linux/pseudo_wrappers.c | 29 ++++++++++++++++-------------
>   1 file changed, 16 insertions(+), 13 deletions(-)
> 
> diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
> index 6b54083..b486c34 100644
> --- a/ports/linux/pseudo_wrappers.c
> +++ b/ports/linux/pseudo_wrappers.c
> @@ -65,19 +65,6 @@ syscall(long number, ...) {
>   		return rc;
>   	}
>   
> -#ifdef SYS_renameat2
> -	/* concerns exist about trying to parse arguments because syscall(2)
> -	 * specifies strange ABI behaviors. If we can get better clarity on
> -	 * that, it could make sense to redirect to wrap_renameat2().
> -	 */
> -	if (number == SYS_renameat2) {
> -		errno = ENOSYS;
> -		return -1;
> -	}
> -#else
> -	(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 */
> @@ -92,6 +79,10 @@ syscall(long number, ...) {
>   	}
>   #endif
>   
> +        if (pseudo_disabled) {
> +                goto call_syscall;
> +        }
> +
>   #ifdef SYS_openat2
>   	/* concerns exist about trying to parse arguments because syscall(2)
>   	 * specifies strange ABI behaviors. If we can get better clarity on
> @@ -105,6 +96,18 @@ syscall(long number, ...) {
>   	}
>   #endif
>   
> +#ifdef SYS_renameat2
> +	/* concerns exist about trying to parse arguments because syscall(2)
> +	 * specifies strange ABI behaviors. If we can get better clarity on
> +	 * that, it could make sense to redirect to wrap_renameat2().
> +	 */
> +	if (number == SYS_renameat2) {
> +		errno = ENOSYS;
> +		return -1;
> +	}
> +#endif
> +
> +call_syscall:

On Debian 11 this fails to compile.  I had to add a patch to workaround what I 
suspect is a compiler bug:

  call_syscall:
+       /* On Debian 11 - gcc (Debian 10.2.1-6) this results in:
+        *   error: a label can only be part of a statement and a declaration
+        *   is not a statement
+        *
+        * adding a ; here resolves this
+        */
+       ;
+




>   	/* 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?
Mark Hatle Jan. 17, 2026, 4:10 p.m. UTC | #2
I'm dropping this patch.  Debian 11 glibc has set more functions as path 
nonnull, so the compiler complains about the EFAULT check of path.

The original issue is still resolved, but if this nonnull behavior continues at 
some point it's going to show what rust is doing to check for xstat is indeed 
sketchy.

--Mark

On 1/15/26 5:43 PM, Mark Hatle wrote:
> Note, other functions claim to return EFAULT when the path element is NULL,
> however glibc has these parameters tagged as non-null which optimizes out
> any sort of NULL test by default.  Due to this only items that don't error
> were included.
> 
> Signed-off-by: Mark Hatle <mark.hatle@kernel.crashing.org>
> ---
>   ports/linux/openat2/wrapfuncs.in |  2 +-
>   ports/linux/wrapfuncs.in         | 22 +++++++++++-----------
>   ports/linux/xattr/wrapfuncs.in   | 16 ++++++++--------
>   3 files changed, 20 insertions(+), 20 deletions(-)
> 
> diff --git a/ports/linux/openat2/wrapfuncs.in b/ports/linux/openat2/wrapfuncs.in
> index 96ae8a7..419813f 100644
> --- a/ports/linux/openat2/wrapfuncs.in
> +++ b/ports/linux/openat2/wrapfuncs.in
> @@ -1 +1 @@
> -int openat2(int dirfd, const char *path, struct open_how *how, size_t size);
> +int openat2(int dirfd, const char *path, struct open_how *how, size_t size); /* efault=path */
> diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in
> index 234a13c..7a2e9f0 100644
> --- a/ports/linux/wrapfuncs.in
> +++ b/ports/linux/wrapfuncs.in
> @@ -1,17 +1,17 @@
>   int open(const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), 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 __xstat(int ver, const char *path, struct stat *buf); /* efault=path */
> +int __lxstat(int ver, const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW, efault=path */
>   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 __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags); /* efault=path */
>   int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
>   int __openat_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), 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 __xmknod(int ver, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW, efault=path */
> +int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW, efault=path */
>   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
> @@ -24,15 +24,15 @@ 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 fstatat(int dirfd, const char *path, struct stat *buf, int flags); /* efault=path */
>   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 fstatat64(int dirfd, const char *path, struct stat64 *buf, int flags); /* efault=path */
> +int __xstat64(int ver, const char *path, struct stat64 *buf); /* efault=path */
> +int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLINK_NOFOLLOW, efault=path */
>   int __fxstat64(int ver, int fd, struct stat64 *buf);
> -int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags);
> +int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags); /* efault=path */
>   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 */
> @@ -61,7 +61,7 @@ int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **p
>   int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp);
>   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 renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); /* flags=AT_SYMLINK_NOFOLLOW, efault=oldpath|newpath */
>   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/wrapfuncs.in b/ports/linux/xattr/wrapfuncs.in
> index 09eba23..e8045b9 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, 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 getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17", efault=path */
> +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", efault=path */
>   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 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", efault=path */
> +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", efault=path */
>   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 listxattr(const char *path, char *list, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17", efault=path */
> +ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17", efault=path */
>   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 removexattr(const char *path, const char *name); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17", efault=path */
> +int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17", efault=path */
>   int fremovexattr(int filedes, const char *name); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */