From b9dbd006beca9f129e1ad655573d66638d832d4e Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Thu, 5 Nov 2026 00:47:38 -0500 Subject: [PATCH] fix(storage): Windows installs on a natively mounted card no longer ask for an administrator (#2100) * fix(storage): Windows installs on a natively mounted card no longer ask for an administrator Cards mounted directly as FAT, exFAT or NTFS were not treated as Android storage, so wineserver reported an Anonymous owner and refused permission changes. Its chmod on these volumes also no longer fails the request. * fix(storage): keep NTFS owners and the chmod error --- tools/linuxfs/preload/owner.c | 34 +++++++++++++++++++++-- tools/tests/test_storage_owner_preload.py | 34 +++++++++++++++++++---- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/tools/linuxfs/preload/owner.c b/tools/linuxfs/preload/owner.c index d4e42964c..78ada3bf0 100644 --- a/tools/linuxfs/preload/owner.c +++ b/tools/linuxfs/preload/owner.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include #include #include #include @@ -8,6 +9,8 @@ #define FUSE_MAGIC 0x65735546 #define SDCARDFS_MAGIC 0x5dca2df5 +#define FAT_MAGIC 0x4d44 +#define EXFAT_MAGIC 0x2011bab0 static int is_wineserver(void) { static int cached = -1; @@ -27,15 +30,42 @@ static int is_wineserver(void) { return cached; } +static int has_no_unix_owner(int fd) { + struct statfs fs; + + if (fstatfs(fd, &fs) != 0) return 0; + return fs.f_type == FUSE_MAGIC || fs.f_type == SDCARDFS_MAGIC || fs.f_type == FAT_MAGIC || + (unsigned int)fs.f_type == EXFAT_MAGIC; +} + __attribute__((visibility("hidden"))) void bl_storage_owner(int fd, struct stat *st) { - struct statfs fs; int saved = errno; uid_t uid; if (st != NULL && is_wineserver()) { uid = getuid(); - if (st->st_uid != uid && fstatfs(fd, &fs) == 0 && (fs.f_type == FUSE_MAGIC || fs.f_type == SDCARDFS_MAGIC)) st->st_uid = uid; + if (st->st_uid != uid && has_no_unix_owner(fd)) st->st_uid = uid; } errno = saved; } + +int fchmod(int fd, mode_t mode) { + static int (*next_fchmod)(int, mode_t); + int saved = errno, result; + + if (next_fchmod == NULL) next_fchmod = dlsym(RTLD_NEXT, "fchmod"); + if (next_fchmod == NULL) { + errno = ENOSYS; + return -1; + } + result = next_fchmod(fd, mode); + if (result != 0 && errno == EPERM && is_wineserver()) { + if (has_no_unix_owner(fd)) { + errno = saved; + return 0; + } + errno = EPERM; + } + return result; +} diff --git a/tools/tests/test_storage_owner_preload.py b/tools/tests/test_storage_owner_preload.py index a66719860..42f66c61e 100644 --- a/tools/tests/test_storage_owner_preload.py +++ b/tools/tests/test_storage_owner_preload.py @@ -11,7 +11,9 @@ FAKE = r""" #define _GNU_SOURCE #include #include +#include #include +#include #include #include #include @@ -29,8 +31,22 @@ int fstatfs(int fd, struct statfs *out) { if (result == 0 && strstr(path, "/fuse/")) out->f_type = 0x65735546; if (result == 0 && strstr(path, "/sdcardfs/")) out->f_type = 0x5dca2df5; if (result == 0 && strstr(path, "/ext4/")) out->f_type = 0xef53; + if (result == 0 && strstr(path, "/fat/")) out->f_type = 0x4d44; + if (result == 0 && strstr(path, "/exfat/")) out->f_type = 0x2011bab0; + if (result == 0 && strstr(path, "/ntfs/")) out->f_type = 0x5346544e; + if (strstr(path, "/broken/")) { + errno = EIO; + return -1; + } return result; } + +int fchmod(int fd, mode_t mode) { + (void)fd; + (void)mode; + errno = EPERM; + return -1; +} """ PROGRAM = r""" @@ -50,6 +66,8 @@ int main(int argc, char **argv) { errno = 1234; int result = fstat(fd, &st); printf("%s %d\n", st.st_uid == real ? "real" : st.st_uid == real + 1 ? "user" : "other", result == 0 && errno == 1234); + errno = 0; + printf("%s\n", fchmod(fd, 0755) == 0 ? "chmod-ok" : errno == EPERM ? "chmod-denied" : "chmod-other"); close(fd); } return 0; @@ -76,7 +94,7 @@ class StorageOwnerPreloadTest(unittest.TestCase): subprocess.run(["cc", "-O2", "-o", str(cls.wineserver), str(build / "program.c")], check=True) cls.other = build / "wine64-preloader" shutil.copy(cls.wineserver, cls.other) - for kind in ("fuse", "sdcardfs", "ext4"): + for kind in ("fuse", "sdcardfs", "ext4", "fat", "exfat", "ntfs", "broken"): (cls.dir / kind).mkdir() (cls.dir / kind / "file").write_text("x") @@ -85,20 +103,26 @@ class StorageOwnerPreloadTest(unittest.TestCase): cls.temp.cleanup() def owners(self, program, *kinds): - env = dict(os.environ, LD_PRELOAD="%s:%s" % (self.fake, self.preload)) + env = dict(os.environ, LD_PRELOAD="%s:%s" % (self.preload, self.fake)) env.pop("BL_STORAGE_DIAGNOSTICS", None) paths = [str(self.dir / kind / "file") for kind in kinds] result = subprocess.run([str(program)] + paths, env=env, capture_output=True, text=True, check=True) return result.stdout.split() def test_wineserver_owns_what_is_on_android_storage(self): - self.assertEqual(["user", "1", "user", "1"], self.owners(self.wineserver, "fuse", "sdcardfs")) + self.assertEqual(["user", "1", "chmod-ok"] * 2, self.owners(self.wineserver, "fuse", "sdcardfs")) + + def test_wineserver_owns_what_is_on_a_native_card(self): + self.assertEqual(["user", "1", "chmod-ok"] * 2, self.owners(self.wineserver, "fat", "exfat")) def test_other_filesystems_keep_their_owner(self): - self.assertEqual(["real", "1"], self.owners(self.wineserver, "ext4")) + self.assertEqual(["real", "1", "chmod-denied"] * 2, self.owners(self.wineserver, "ext4", "ntfs")) + + def test_chmod_keeps_its_error_when_the_volume_is_unknown(self): + self.assertEqual(["real", "1", "chmod-denied"], self.owners(self.wineserver, "broken")) def test_other_programs_see_the_real_owner(self): - self.assertEqual(["real", "1", "real", "1"], self.owners(self.other, "fuse", "sdcardfs")) + self.assertEqual(["real", "1", "chmod-denied"] * 2, self.owners(self.other, "fuse", "sdcardfs")) if __name__ == "__main__":