Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions testsuite/partial-protected-regular-retry-linux_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@

hook_code = r'''
#define _GNU_SOURCE
/* Build the hook itself WITHOUT large-file redirection, whatever the compiler
* defaults to. Debian's armhf/hppa/powerpc gcc predefines
* -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 (check with "gcc -v -E -"), and under
* those macros glibc's __REDIRECT renames the DEFINITIONS below -- open()
* becomes open64(), fstatat() becomes __fstatat64_time64() -- which then
* collide with the explicit large-file wrappers further down ("symbol `open64'
* is already defined"). Undefining them here keeps each name declared exactly
* once, so the hook always exports both spellings and interposes whichever set
* the rsync under test was linked against. */
#undef _FILE_OFFSET_BITS
#undef _TIME_BITS
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
Expand All @@ -56,6 +67,9 @@
static int (*real_openat)(int, const char *, int, ...);
static int (*real_fstatat)(int, const char *, struct stat *, int);
static int (*real_fxstatat)(int, int, const char *, struct stat *, int);
static int (*real_fstatat64)(int, const char *, struct stat64 *, int);
static int (*real_fxstatat64)(int, int, const char *, struct stat64 *, int);
static int (*real_fstatat64_time64)(int, const char *, struct stat64 *, int);

/* Resolve on demand rather than trusting our constructor to have run. A
* preloaded open() interposes for the whole process the moment the loader maps
Expand All @@ -77,6 +91,10 @@
if (!real_openat) real_openat = dlsym(RTLD_NEXT, "openat");
if (!real_fstatat) real_fstatat = dlsym(RTLD_NEXT, "fstatat");
if (!real_fxstatat) real_fxstatat = dlsym(RTLD_NEXT, "__fxstatat");
if (!real_fstatat64) real_fstatat64 = dlsym(RTLD_NEXT, "fstatat64");
if (!real_fxstatat64) real_fxstatat64 = dlsym(RTLD_NEXT, "__fxstatat64");
if (!real_fstatat64_time64)
real_fstatat64_time64 = dlsym(RTLD_NEXT, "__fstatat64_time64");
hook_resolving = 0;
}

Expand Down Expand Up @@ -151,6 +169,10 @@
# define HOOK_TAKES_MODE(f) ((f) & O_CREAT)
#endif

#ifndef O_LARGEFILE
# define O_LARGEFILE 0
#endif

static int is_victim_write(const char *path, int flags)
{
return path && strcmp(path, "victim") == 0
Expand Down Expand Up @@ -231,6 +253,49 @@
return fd;
}

/* --- large-file spellings -------------------------------------------------
* Which names the RECEIVER calls is settled by its own build: where off_t is
* not already 64 bits, configure's AC_SYS_LARGEFILE adds -D_FILE_OFFSET_BITS=64
* (i386, alpha, ...), and distro CPPFLAGS add it -- along with -D_TIME_BITS=64
* -- on the 64-bit time_t ports, so glibc redirects each open()/openat()/
* fstatat() call to open64()/openat64()/fstatat64()/__fstatat64_time64().
* "objdump -T rsync | grep UND" says which set a given build imports.
*
* Which names THIS HOOK exports is a different question with a different
* answer, settled by whatever the "cc" below it defaults to -- see the #undef
* at the top. Nothing keeps the two in step, so define every spelling and let
* the loader match them up. With only the unsuffixed ones the receiver's opens
* sail straight past the hook, no EACCES is ever injected, and the test reports
* "positive control failed" having exercised nothing at all.
*
* O_LARGEFILE is the only thing open64() adds over open(), so the wrappers
* below can hand the call to the unsuffixed interposer above. */
int open64(const char *path, int flags, ...)
{
mode_t mode = 0;

if (HOOK_TAKES_MODE(flags)) {
va_list ap;
va_start(ap, flags);
mode = (mode_t)va_arg(ap, int);
va_end(ap);
}
return open(path, flags | O_LARGEFILE, mode);
}

int openat64(int dfd, const char *path, int flags, ...)
{
mode_t mode = 0;

if (HOOK_TAKES_MODE(flags)) {
va_list ap;
va_start(ap, flags);
mode = (mode_t)va_arg(ap, int);
va_end(ap);
}
return openat(dfd, path, flags | O_LARGEFILE, mode);
}

/* ona_open() decides via fstatat(..., AT_SYMLINK_NOFOLLOW) and refuses a
* component owned by neither root nor the euid. Model the attacker as a
* different uid so a retry that kept the ownership walk refuses the swap. */
Expand Down Expand Up @@ -276,6 +341,68 @@
return rc;
}

/* The stat family's large-file spellings. st_mode and st_uid sit ahead of the
* timestamps in every glibc struct stat layout, so the time32/time64 variants
* of the buffer are interchangeable for the two fields touched here. */
static void model_foreign_owner64(int rc, const char *path, struct stat64 *st)
{
if (rc == 0 && swapped && path && strcmp(path, "pdir") == 0
&& S_ISLNK(st->st_mode)) {
st->st_uid = geteuid() + 1;
mark(getenv("RSYNC_PARTIAL_RETRY_FOREIGN_MARKER"));
}
}

int fstatat64(int dfd, const char *path, struct stat64 *st, int flags)
{
int rc, saved_errno;

hook_resolve();
if (!real_fstatat64) {
errno = ENOSYS;
return -1;
}
rc = real_fstatat64(dfd, path, st, flags);
saved_errno = errno;
model_foreign_owner64(rc, path, st);
errno = saved_errno;
return rc;
}

int __fxstatat64(int ver, int dfd, const char *path, struct stat64 *st, int flags)
{
int rc, saved_errno;

hook_resolve();
if (!real_fxstatat64) {
errno = ENOSYS;
return -1;
}
rc = real_fxstatat64(ver, dfd, path, st, flags);
saved_errno = errno;
model_foreign_owner64(rc, path, st);
errno = saved_errno;
return rc;
}

/* A 32-bit port built with -D_TIME_BITS=64 (Debian's armhf/armel/hppa/powerpc,
* ...) reaches fstatat() under this third name. */
int __fstatat64_time64(int dfd, const char *path, struct stat64 *st, int flags)
{
int rc, saved_errno;

hook_resolve();
if (!real_fstatat64_time64) {
errno = ENOSYS;
return -1;
}
rc = real_fstatat64_time64(dfd, path, st, flags);
saved_errno = errno;
model_foreign_owner64(rc, path, st);
errno = saved_errno;
return rc;
}

__attribute__((constructor)) static void hook_loaded(void)
{
hook_resolve();
Expand Down
Loading