Skip to content

Commit 161e59c

Browse files
committed
Patch DatabaseViewerActivity.onCreate to ensure only valid file paths are processed
1 parent 2a857ac commit 161e59c

File tree

3 files changed

+127
-5
lines changed

3 files changed

+127
-5
lines changed

app/src/main/java/com/amaze/filemanager/ui/activities/DatabaseViewerActivity.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.amaze.filemanager.filesystem.root.CopyFilesCommand;
3434
import com.amaze.filemanager.ui.activities.superclasses.ThemedActivity;
3535
import com.amaze.filemanager.ui.fragments.DbViewerFragment;
36+
import com.amaze.filemanager.utils.Utils;
3637

3738
import android.database.Cursor;
3839
import android.database.sqlite.SQLiteDatabase;
@@ -73,9 +74,9 @@ public void onCreate(Bundle savedInstanceState) {
7374
boolean useNewStack = getBoolean(PREFERENCE_TEXTEDITOR_NEWSTACK);
7475
getSupportActionBar().setDisplayHomeAsUpEnabled(!useNewStack);
7576

76-
path = getIntent().getStringExtra("path");
77+
path = Utils.sanitizeInput(getIntent().getStringExtra("path"));
7778

78-
if (path == null) {
79+
if (!isValidPath(path)) {
7980
Toast.makeText(this, R.string.operation_not_supported, Toast.LENGTH_SHORT).show();
8081
finish();
8182
return;
@@ -163,22 +164,51 @@ protected void onDestroy() {
163164

164165
@Override
165166
public boolean onPrepareOptionsMenu(Menu menu) {
166-
toolbar.setTitle(pathFile.getName());
167+
setToolbarTitle();
167168
return super.onPrepareOptionsMenu(menu);
168169
}
169170

170171
@Override
171172
public boolean onOptionsItemSelected(MenuItem item) {
172173
if (item.getItemId() == android.R.id.home) {
173174
onBackPressed();
174-
toolbar.setTitle(pathFile.getName());
175+
setToolbarTitle();
175176
}
176177
return super.onOptionsItemSelected(item);
177178
}
178179

179180
@Override
180181
public void onBackPressed() {
181182
super.onBackPressed();
182-
toolbar.setTitle(pathFile.getName());
183+
setToolbarTitle();
184+
}
185+
186+
private void setToolbarTitle() {
187+
if (pathFile != null) {
188+
toolbar.setTitle(pathFile.getName());
189+
}
190+
}
191+
192+
private boolean isValidPath(String path) {
193+
// Check if the path is not null, not empty, and does not contain special characters
194+
if (path == null || path.isEmpty()) {
195+
return false;
196+
}
197+
198+
try {
199+
File file = new File(path);
200+
String canonicalPath = file.getCanonicalPath();
201+
// Prevent path traversal attacks
202+
if (canonicalPath.contains("..")) {
203+
return false;
204+
}
205+
// Check for valid database file extensions
206+
if (!path.endsWith(".db") && !path.endsWith(".sqlite") && !path.endsWith(".sqlite3")) {
207+
return false;
208+
}
209+
return file.exists() && file.isFile();
210+
} catch (Exception e) {
211+
return false;
212+
}
183213
}
184214
}

app/src/main/java/com/amaze/filemanager/utils/Utils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ public static boolean isDeviceInLandScape(Activity activity) {
230230

231231
/** Sanitizes input from external application to avoid any attempt of command injection */
232232
public static String sanitizeInput(String input) {
233+
if (input == null || input.isEmpty()) {
234+
return input;
235+
}
236+
233237
// iterate through input and keep sanitizing until it's fully injection proof
234238
String sanitizedInput;
235239
String sanitizedInputTemp = input;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.amaze.filemanager.ui.activities
2+
3+
import android.content.Intent
4+
import android.os.Build
5+
import android.os.Build.VERSION_CODES.LOLLIPOP
6+
import android.os.Build.VERSION_CODES.P
7+
import androidx.test.ext.junit.runners.AndroidJUnit4
8+
import com.amaze.filemanager.shadows.ShadowMultiDex
9+
import io.mockk.every
10+
import io.mockk.spyk
11+
import org.awaitility.Awaitility.await
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import org.robolectric.Robolectric
15+
import org.robolectric.annotation.Config
16+
import org.robolectric.shadows.ShadowStorageManager
17+
import org.robolectric.util.ReflectionHelpers
18+
import java.io.File
19+
import java.util.concurrent.TimeUnit
20+
21+
/**
22+
* Tests for [DatabaseViewerActivity].
23+
*/
24+
@RunWith(AndroidJUnit4::class)
25+
@Config(
26+
sdk = [LOLLIPOP, P, Build.VERSION_CODES.R],
27+
shadows = [ShadowMultiDex::class, ShadowStorageManager::class],
28+
)
29+
class DatabaseViewerActivityTest {
30+
/**
31+
* Tests that the activity sanitizes the path in the intent to prevent malicious payloads
32+
* from being executed.
33+
*/
34+
@Test
35+
fun testSanitizePathInIntent() {
36+
val maliciousPayload = "/sdcard/fake.db; uname -a > /sdcard/system_info.txt; echo"
37+
38+
val intent = Intent().putExtra("path", maliciousPayload)
39+
40+
val activity =
41+
Robolectric.buildActivity(
42+
DatabaseViewerActivity::class.java,
43+
intent,
44+
)
45+
.create().start().visible().get()
46+
47+
await().atMost(10, TimeUnit.SECONDS).until {
48+
activity.isFinishing &&
49+
ReflectionHelpers.getField<File>(activity, "pathFile") == null
50+
}
51+
}
52+
53+
/**
54+
* Tests that the activity does not crash when the path in the intent is valid.
55+
*/
56+
@Test
57+
fun testValidPathInIntent() {
58+
// Create a temporary valid database file
59+
val validDbFile = File.createTempFile("test", ".db")
60+
validDbFile.deleteOnExit()
61+
62+
val intent = Intent().putExtra("path", validDbFile.absolutePath)
63+
64+
val activityController =
65+
Robolectric.buildActivity(
66+
DatabaseViewerActivity::class.java,
67+
intent,
68+
)
69+
.create()
70+
71+
val activity = activityController.get()
72+
val spyActivity = spyk(activity, recordPrivateCalls = true)
73+
74+
// Mock the load method to do nothing
75+
every { spyActivity invoke ("load") withArguments listOf(any<File>()) } returns Unit
76+
77+
activityController.start().visible()
78+
79+
await().atMost(5, TimeUnit.SECONDS).until {
80+
!spyActivity.isFinishing &&
81+
ReflectionHelpers
82+
.getField<File>(spyActivity, "pathFile")?.absolutePath == validDbFile.absolutePath
83+
}
84+
85+
// Clean up
86+
validDbFile.delete()
87+
}
88+
}

0 commit comments

Comments
 (0)