#10887·syncthing

Android's 2026-09 security patches cause file permission revocation during atomic file replacement

Author: chenxiaolongCreated Sep 16, 2026Updated Sep 16, 2026
Labelsbugneeds-triage

What happened?

Android's new security patches this month introduced a patch in the MediaProvider component that causes an external app's permissions to files within a shared folder to be revoked when Syncthing replaces an existing regular file. This affects the most common scenario where an app, such as the KeePassDX password manager, opens a file from the shared folder using Android's storage access framework. (This has no effect on Syncthing itself since it requires permissions to the full internal storage anyway.)

When replacing an existing regular file, sendReceiveFolder.performFinish() normally does an (almost) atomic replace where it writes the new data to a temp file, deletes the target file, and then renames the temp file to the target file. Unfortunately, this Android security patch revokes permissions during both unlink and rename, making atomic replacements impossible even if Syncthing didn't have the explicit delete in the middle and relied solely on POSIX atomic renames.

I tested this on a userdebug Android build where I added a couple log statements to MediaProvider. You can see it revoke permissions:

2026-09-15 23:32:47.271004 -0400 E/WTF     ( 4348): revokeUriPermissionGrantsOnDocumentsProvider(/storage/emulated/0/a)
2026-09-15 23:32:47.311661 -0400 E/WTF     ( 4348): revokeUriPermissionGrantsOnPathChange(/storage/emulated/0/b, /storage/emulated/0/a)
2026-09-15 23:32:47.314857 -0400 E/WTF     ( 4348): revokeUriPermissionGrantsOnDocumentsProvider(/storage/emulated/0/b)

when I run:

mustang:/sdcard $ touch a b
mustang:/sdcard $ strace mv b a
execve("/system/bin/mv", ["mv", "b", "a"], 0xef57e1ff5ed0 /* 26 vars */) = 0
...
newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0660, st_size=0, ...}, 0) = 0
umask(000)                              = 000
newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0660, st_size=0, ...}, 0) = 0
renameat2(AT_FDCWD, "b", AT_FDCWD, "a", 0) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

In BasicSync, I'm working around this with https://github.com/chenxiaolong/syncthing/commit/668fe245a4ccb97231a534dad85737d4a9e2e1f4 (https://github.com/chenxiaolong/BasicSync/pull/245). It's an ugly hack to force the fallback path of osutil.RenameOrCopy() to be taken when performFinish() replaces a regular file on Android's MediaProvider FUSE filesystem.

I don't expect something like this to be merged upstream, but I'm curious if anyone has other ideas for making Syncthing optionally overwrite regular files in place.

Syncthing version

v2.1.5

Platform & operating system

Android arm64