[pseudo][PATCH 05/11] ports/unix: fts_*: Certain functions were incorrectly returning stat data
Mark Hatle <[email protected]>
| Newsgroups | org.yoctoproject.lists.yocto-patches |
|---|---|
| Message-ID | <[email protected]> |
From: Mark Hatle <[email protected]> Glibc appears to be using fts private symbols for stat/fstatat/etc. This caused incorrect ownership and permissions data to be returned from fts_read(). It appears that glibc's fts_read calls directly into __fstatat64 instead of using the standard fstatat64 function. This means fts_read() populates FTSENT->fts_statp with real stat data (actual on-disk uid/gid), not pseudo's fake data. fts_read and fts_children wrappers The fix: wrap fts_read() and fts_children() so that after the real libc function returns, we re-query pseudo's database for each entry. This is the same pattern used by nftw where each callback re-stats each file through pseudo to void internal functions returning actual data. AI-Generate: Fix suggested by github copilot (claude opus 6.4) Signed-off-by: Mark Hatle <[email protected]> Signed-off-by: Mark Hatle <[email protected]> --- ports/unix/guts/fts_children.c | 64 +++++++++++++++++++++++++++++++++++++++ ports/unix/guts/fts_read.c | 68 ++++++++++++++++++++++++++++++++++++++++++ ports/unix/wrapfuncs.in | 2 ++ 3 files changed, 134 insertions(+) create mode 100644 ports/unix/guts/fts_children.c create mode 100644 ports/unix/guts/fts_read.c diff --git a/ports/unix/guts/fts_children.c b/ports/unix/guts/fts_children.c new file mode 100644 index 0000000..f5d1890 --- /dev/null +++ b/ports/unix/guts/fts_children.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Yocto Project; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static FTSENT * + * wrap_fts_children(FTS *ftsp, int options) { + * FTSENT * rc = NULL; + */ + + rc = real_fts_children(ftsp, options); + + /* glibc's fts_children calls fstatat internally using glibc-private + * symbols, which bypass pseudo's LD_PRELOAD wrappers. We need to + * re-stat each entry through pseudo so that pseudo-tracked + * ownership and permissions are visible to callers. + */ + FTSENT *p; + for (p = rc; p != NULL; p = p->fts_link) { + if (p->fts_statp && p->fts_path) { + pseudo_msg_t *msg; + int save_errno = errno; + const char *fts_rpath; + PSEUDO_STATBUF buf64; + + switch (p->fts_info) { + case FTS_F: + case FTS_D: + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + /* See fts_read.c: fts_path may already contain + * the chroot prefix from fts_open's path resolution. + * Use it directly in that case. + */ + if (p->fts_path[0] == '/' && pseudo_chroot_len && + !memcmp(p->fts_path, pseudo_chroot, pseudo_chroot_len) && + (p->fts_path[pseudo_chroot_len] == '/' || + p->fts_path[pseudo_chroot_len] == '\0')) { + fts_rpath = p->fts_path; + } else { + fts_rpath = PSEUDO_ROOT_PATH(AT_FDCWD, p->fts_path, AT_SYMLINK_NOFOLLOW); + } + if (fts_rpath) { + pseudo_stat64_from32(&buf64, p->fts_statp); + msg = pseudo_client_op(OP_STAT, 0, -1, -1, fts_rpath, &buf64); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf64, msg); + pseudo_stat32_from64(p->fts_statp, &buf64); + } + } + break; + default: + break; + } + + errno = save_errno; + } + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fts_read.c b/ports/unix/guts/fts_read.c new file mode 100644 index 0000000..654b982 --- /dev/null +++ b/ports/unix/guts/fts_read.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2026 Yocto Project; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static FTSENT * + * wrap_fts_read(FTS *ftsp) { + * FTSENT * rc = NULL; + */ + + rc = real_fts_read(ftsp); + + /* glibc's fts_read calls fstatat internally using glibc-private + * symbols, which bypass pseudo's LD_PRELOAD wrappers. We need to + * re-stat the entry through pseudo so that pseudo-tracked + * ownership and permissions are visible to callers. + */ + if (rc && rc->fts_statp && rc->fts_accpath) { + pseudo_msg_t *msg; + int save_errno = errno; + const char *fts_rpath; + PSEUDO_STATBUF buf64; + + switch (rc->fts_info) { + case FTS_F: + case FTS_D: + case FTS_DP: + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + /* fts_open passes real (chroot-resolved) paths to real_fts_open, + * so fts_path is already a real filesystem path. We must use + * it directly without PSEUDO_ROOT_PATH, which would + * incorrectly prepend the chroot prefix a second time. + * + * However, for the non-chroot case (or when fts was given + * relative paths that are still relative in fts_path), + * we still need PSEUDO_ROOT_PATH to make them absolute. + */ + if (rc->fts_path[0] == '/' && pseudo_chroot_len && + !memcmp(rc->fts_path, pseudo_chroot, pseudo_chroot_len) && + (rc->fts_path[pseudo_chroot_len] == '/' || + rc->fts_path[pseudo_chroot_len] == '\0')) { + /* Already a real path with chroot prefix */ + fts_rpath = rc->fts_path; + } else { + fts_rpath = PSEUDO_ROOT_PATH(AT_FDCWD, rc->fts_path, AT_SYMLINK_NOFOLLOW); + } + if (fts_rpath) { + pseudo_stat64_from32(&buf64, rc->fts_statp); + msg = pseudo_client_op(OP_STAT, 0, -1, -1, fts_rpath, &buf64); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf64, msg); + pseudo_stat32_from64(rc->fts_statp, &buf64); + } + } + break; + default: + break; + } + + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in index 7724fc7..5665735 100644 --- a/ports/unix/wrapfuncs.in +++ b/ports/unix/wrapfuncs.in @@ -13,6 +13,8 @@ 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 */ +FTSENT *fts_read(FTS *ftsp); /* noignore_path=1, inode64=1 */ +FTSENT *fts_children(FTS *ftsp, int options); /* noignore_path=1, 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); int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob); -- 1.8.3.1