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__":