zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
- util1.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o
+ util1.o util2.o main.o checksum.o match.o syscall.o android.o log.o backup.o delete.o
OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@
popt/popthelp.o popt/poptparse.o popt/poptint.o
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
-TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
+TLS_OBJ = tls.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
# Programs we must have to run the test cases
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
getfsdev$(EXEEXT): getfsdev.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
-TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o
+TRIMSLASH_OBJ = trimslash.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o
trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
-T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
+T_UNSAFE_OBJ = t_unsafe.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
-T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
+T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)
-T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
+T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)
--- /dev/null
+/*
+ * Android-specific helpers.
+ *
+ * openat2() usability probe
+ * -------------------------
+ * openat2(2) is invoked directly via syscall() because the C library lacked a
+ * wrapper for it for years. Under a seccomp filter that uses
+ * SECCOMP_RET_TRAP -- as the Android application sandbox does -- a disallowed
+ * syscall raises SIGSYS and *kills the process* rather than failing with
+ * ENOSYS, so inspecting errno after the call is too late. We therefore probe
+ * openat2() once, behind a temporary SIGSYS handler, so a trapped syscall is
+ * caught and secure_relative_open_linux() can fall back to the portable
+ * per-component O_NOFOLLOW resolver instead of the whole process dying.
+ *
+ * This is only needed on Android, so the probe body is compiled only there.
+ * __ANDROID__ is defined by Bionic's headers and reflects the *target*, not
+ * the build host: it is set both for NDK cross-compiles (from a Linux/macOS
+ * host) and for native Termux builds, and is unset on every other platform.
+ * That makes it a reliable compile-time switch for cross builds -- there is
+ * nothing to detect in configure. Everywhere else openat2() is never
+ * seccomp-trapped to SIGSYS (a missing syscall simply returns ENOSYS), so
+ * openat2_usable() collapses to a constant 1 with no run-time cost.
+ */
+
+#include "rsync.h"
+
+#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
+
+#include <setjmp.h>
+#include <sys/syscall.h>
+#include <linux/openat2.h>
+
+static sigjmp_buf openat2_probe_env;
+
+static void openat2_probe_handler(int signo)
+{
+ (void)signo;
+ siglongjmp(openat2_probe_env, 1);
+}
+
+#endif
+
+int openat2_usable(void)
+{
+#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
+ static int cached = -1;
+ struct sigaction sa, old_sa;
+
+ if (cached >= 0)
+ return cached;
+
+ memset(&sa, 0, sizeof sa);
+ sa.sa_handler = openat2_probe_handler;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGSYS, &sa, &old_sa) != 0)
+ return cached = 0;
+
+ if (sigsetjmp(openat2_probe_env, 1) != 0) {
+ /* SIGSYS delivered: openat2 is blocked by a seccomp filter. */
+ cached = 0;
+ } else {
+ struct open_how how;
+ int fd;
+ memset(&how, 0, sizeof how);
+ how.flags = O_RDONLY | O_DIRECTORY;
+ how.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS;
+ fd = syscall(SYS_openat2, AT_FDCWD, ".", &how, sizeof how);
+ if (fd >= 0)
+ close(fd);
+ /* Usable only if the probe actually succeeded. Any failure --
+ * ENOSYS (kernel < 5.6), a seccomp SECCOMP_RET_ERRNO denial
+ * (EPERM/EACCES), or EINVAL (RESOLVE_BENEATH unsupported) --
+ * means we must fall back to the portable O_NOFOLLOW walk. */
+ cached = fd >= 0;
+ }
+
+ sigaction(SIGSYS, &old_sa, NULL);
+ return cached;
+#else
+ return 1;
+#endif
+}
}
#if defined(__linux__) && defined(HAVE_OPENAT2)
+/* openat2(RESOLVE_BENEATH) via the raw syscall, gated on openat2_usable() so a
+ * seccomp filter that traps openat2 with SIGSYS (e.g. the Android sandbox)
+ * makes us report ENOSYS and fall back rather than killing the process. Only
+ * the openat2 call is gated here; a plain openat() is always safe to attempt. */
+static int openat2_beneath(int dirfd, const char *path, const struct open_how *how)
+{
+ if (!openat2_usable()) {
+ errno = ENOSYS;
+ return -1;
+ }
+ return syscall(SYS_openat2, dirfd, path, how, sizeof *how);
+}
+
static int secure_relative_open_linux(const char *basedir, const char *relpath, int flags, mode_t mode)
{
struct open_how how;
memset(&bhow, 0, sizeof bhow);
bhow.flags = O_RDONLY | O_DIRECTORY;
bhow.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS;
- dirfd = syscall(SYS_openat2, AT_FDCWD, basedir, &bhow, sizeof bhow);
+ dirfd = openat2_beneath(AT_FDCWD, basedir, &bhow);
if (dirfd == -1)
return -1;
}
- retfd = syscall(SYS_openat2, dirfd, relpath, &how, sizeof how);
+ retfd = openat2_beneath(dirfd, relpath, &how);
if (dirfd != AT_FDCWD)
close(dirfd);
* other than the kernel rejecting the requested confinement flag. */
static int kernel_resolve_beneath_supported(void)
{
+#if (defined(__linux__) && defined(HAVE_OPENAT2)) || defined(O_RESOLVE_BENEATH)
int fd;
+#endif
#if defined(__linux__) && defined(HAVE_OPENAT2)
- {
+ if (openat2_usable()) {
struct open_how how;
memset(&how, 0, sizeof how);
how.flags = O_RDONLY | O_DIRECTORY;
close(fd);
return 1;
}
- /* ENOSYS = kernel < 5.6. Fall through to the O_RESOLVE_BENEATH
+ /* ENOSYS = kernel < 5.6 or openat2 seccomp-blocked. Fall through to the O_RESOLVE_BENEATH
* probe in case we're a Linux build running on a kernel that
* gained O_RESOLVE_BENEATH via some out-of-tree backport. */
}