Files
maxjivi05 8a6877423e AppImage apps: Electron programs such as Heroic open under FEX
Every Electron AppImage hung without a window or crashed at its first page.
Four causes, each enough on its own:

- The session preload's hooks found the real functions with
  dlsym(RTLD_NEXT). Before glibc 2.34 a thread's first dlsym callocs, and
  Chromium's PartitionAlloc calls syscall() from inside its first malloc:
  the allocator waited for itself. On older glibc (the x86 runtime's 2.31)
  next.c now walks the libraries after the preload with no allocation and
  picks each function's default version, as glibc 2.36 does; the old
  lookup handed realpath() and posix_spawn() their compat versions, and
  realpath(path, NULL) failed. arm64 keeps dlsym.
- libpci's sysfs backend exits when /sys/bus/pci/devices is listable but a
  device's files are not (a RedMagic's PCIe root port), taking every
  Chromium GPU process with it. An empty directory is bound over it on
  devices where that happens.
- FEX_OUTPUTLOG=stderr made FEX log to its own duplicate of descriptor 2.
  Chromium dup2()s its IPC socket onto that low number in each child it
  starts, then lists /proc/self/fd, which FEX logs into the socket. Empty,
  FEX writes to descriptor 2 itself.
- The tracer preload served every /proc/<pid>/status as a memfd, which is
  open for writing. Chromium hands each renderer its status as a read-only
  file and checks that it is one. Only a process's own status (pid, tid,
  self, thread-self) is rewritten now.
2026-10-11 00:17:55 -04:00

73 lines
3.4 KiB
C

/*
* Lock files taken by hard link, which Android denies apps.
*
* libICE and libXau lock an authority file the classic way: create "<file>-c", then link() it to
* "<file>-l" - the link either appears or fails because the other holder's is there. Android's
* SELinux policy refuses hard links in an app's data, so the link fails for everyone, the lock is
* never taken, and Plasma's session manager gives up at "Could not lock ICEAuthority file". With
* no session manager there is no Plasma desktop.
*
* Only that convention is answered here: when the real link is refused and the new name ends in
* "-l", a symlink stands in. Creating a symlink fails on an existing name just as a link does, so
* the lock excludes as before, and the holder removes both names when it is done. Every other
* link keeps the real answer: a program that links a file into place and then deletes the
* original would be left with a dangling symlink.
*
* That pattern itself is answered for one caller that asks: with DROIDDECK_LINK_RENAME=1 (the
* session sets it for localedef, which names a finished locale archive that way) a refused link
* becomes a rename that will not replace an existing name, which is all the link promised there.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
__attribute__((visibility("hidden"))) void *bl_next(const char *name);
static int refused(int error) { return error == EPERM || error == EACCES; }
static int moves_instead(void) {
const char *v = getenv("DROIDDECK_LINK_RENAME");
return v != NULL && strcmp(v, "1") == 0;
}
static int lock_name(const char *path) {
size_t n = path ? strlen(path) : 0;
return n > 2 && strcmp(path + n - 2, "-l") == 0;
}
/* The answer to link(oldpath, newpath), given the real call's: a refused lock link becomes a symlink. */
__attribute__((visibility("hidden"))) int bl_link_lock(const char *oldpath, const char *newpath, int result) {
/* libc's own symlink: dircache.c's wrapper may be holding its lock around this call. */
static int (*real_symlink)(const char *, const char *);
if (result == 0 || !refused(errno)) return result;
if (moves_instead()) return renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_NOREPLACE);
if (!lock_name(newpath)) return result;
if (real_symlink == NULL) real_symlink = bl_next("symlink");
return real_symlink(oldpath, newpath);
}
/* 64-bit builds wrap link() in dircache.c, which hands its result to bl_link_lock. */
#if __SIZEOF_POINTER__ != 8
int link(const char *oldpath, const char *newpath) {
static int (*real_link)(const char *, const char *);
if (real_link == NULL) real_link = bl_next("link");
return bl_link_lock(oldpath, newpath, real_link(oldpath, newpath));
}
#endif
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) {
static int (*real_linkat)(int, const char *, int, const char *, int);
if (real_linkat == NULL) real_linkat = bl_next("linkat");
int result = real_linkat(olddirfd, oldpath, newdirfd, newpath, flags);
if (result != 0 && refused(errno) && flags == 0 && moves_instead())
return renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
/* A relative old name would resolve against the new name's directory as a symlink's target. */
if (result != 0 && refused(errno) && lock_name(newpath) && oldpath[0] == '/' && flags == 0)
return symlinkat(oldpath, newdirfd, newpath);
return result;
}