syncthing / syncthing/syncthing

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

Open
#10,887 1 comment 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs-triage
Dominant language
Go
Stars
88.8k
Forks
5.5k
Avg merge
2d 10h
Merged PRs (30d)
5

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with sendReceiveFolder.performFinish() and the osutil.RenameOrCopy() fallback, then compare the Android-specific workaround in commit 668fe245a4ccb97231a534dad85737d4a9e2e1f4. Reproduce the replacement behavior on Android's MediaProvider FUSE filesystem and investigate whether an upstream-compatible approach exists. Done would require a clearly scoped, tested way to preserve external app permissions, if one is feasible.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, go
Domain
mobile-dev, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.