-
-
Notifications
You must be signed in to change notification settings - Fork 144
Make LiveSync writes atomic instead of locking the runtime's reads #2022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -357,23 +357,45 @@ private int getLength() { | |
| return lengthInt; | ||
| } | ||
|
|
||
| /* | ||
| * Written through a sibling temp file renamed over the target, because | ||
| * the app keeps running JS while files stream in: rename(2) within a | ||
| * directory is atomic, so a runtime thread that requires this path | ||
| * mid-sync gets either the whole previous file or the whole new one. | ||
| * Writing in place cannot be made safe from the reader side -- the | ||
| * truncate lands before any lock a reader could share, and each runtime | ||
| * (main, every worker) reads on its own thread. | ||
| */ | ||
| private void createOrOverrideFile(String fileName, byte[] content) throws IOException { | ||
| File fileToCreate = prepareFile(fileName); | ||
| try { | ||
| File fileToCreate = new File(DEVICE_APP_DIR, fileName); | ||
| File parentDir = fileToCreate.getParentFile(); | ||
|
|
||
| fileToCreate.getParentFile().mkdirs(); | ||
| FileOutputStream fos = new FileOutputStream(fileToCreate.getCanonicalPath()); | ||
| if(runtime != null) { | ||
| runtime.lock(); | ||
| if (parentDir != null) { | ||
| parentDir.mkdirs(); | ||
| } | ||
|
|
||
| File temp = null; | ||
| try { | ||
| // Same directory as the target: rename is only atomic within one | ||
| // filesystem, and a sibling is the one placement that guarantees it. | ||
| temp = File.createTempFile("livesync", ".tmp", parentDir); | ||
|
|
||
| FileOutputStream fos = new FileOutputStream(temp); | ||
| try { | ||
| fos.write(content); | ||
| } finally { | ||
| fos.close(); | ||
| } | ||
| fos.write(content); | ||
| fos.close(); | ||
|
|
||
| if (!temp.renameTo(fileToCreate)) { | ||
| throw new IOException(String.format("failed to rename %s onto the target", temp.getAbsolutePath())); | ||
| } | ||
| temp = null; | ||
|
Comment on lines
369
to
+393
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Serialize file mutations across LiveSync workers. The server starts one Use one service-scoped mutex or a serial operation queue for both 🧰 Tools🪛 ast-grep (0.45.1)[warning] 369-369: Prevent path traversal (path-traversal-java) [warning] 380-380: Temporary file not deleted (tempfile-delete) 🤖 Prompt for AI Agents |
||
| } catch (Exception e) { | ||
| throw new IOException(String.format("\nLiveSync: failed to write file: %s\nOriginal Exception: %s", fileName, e.toString())); | ||
| } finally { | ||
| if(runtime != null) { | ||
| runtime.unlock(); | ||
| if (temp != null) { | ||
| temp.delete(); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -387,14 +409,6 @@ void deleteRecursive(File fileOrDirectory) { | |
| fileOrDirectory.delete(); | ||
| } | ||
|
|
||
| private File prepareFile(String fileName) { | ||
| File fileToCreate = new File(DEVICE_APP_DIR, fileName); | ||
| if (fileToCreate.exists()) { | ||
| fileToCreate.delete(); | ||
| } | ||
| return fileToCreate; | ||
| } | ||
|
|
||
| /* | ||
| * Reads next bites from input stream. Bytes read depend on passed parameter. | ||
| * */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Restrict
fileNametoDEVICE_APP_DIR.The LiveSync peer controls
fileName. A value with../segments can makeFileOutputStreamwrite outsideDEVICE_APP_DIRunder the app UID. Resolve both paths canonically and reject a target that is not below the canonical app directory.Proposed fix
private void createOrOverrideFile(String fileName, byte[] content) throws IOException { - File fileToCreate = new File(DEVICE_APP_DIR, fileName); + File appDir = new File(DEVICE_APP_DIR).getCanonicalFile(); + File fileToCreate = new File(appDir, fileName).getCanonicalFile(); + String appDirPrefix = appDir.getPath() + File.separator; + if (!fileToCreate.getPath().startsWith(appDirPrefix)) { + throw new IOException("LiveSync file path escapes the app directory"); + } File parentDir = fileToCreate.getParentFile();📝 Committable suggestion
🧰 Tools
🪛 ast-grep (0.45.1)
[warning] 369-369: Prevent path traversal
Context: new File(DEVICE_APP_DIR, fileName)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). Security best practice.
(path-traversal-java)
[warning] 380-380: Temporary file not deleted
Context: File.createTempFile("livesync", ".tmp", parentDir)
Note: [CWE-377] Insecure Temporary File. Security best practice.
(tempfile-delete)
🤖 Prompt for AI Agents
Source: Linters/SAST tools