attempt to prevent shell escape
This commit is contained in:
@@ -150,8 +150,9 @@ class IsoDriveManager(private val context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
// Validate file exists
|
||||
val fileCheck = RootManager.executeCommand("test -f \"$isoPath\" && echo exists")
|
||||
// Validate file exists using shell-safe escaping
|
||||
val safePath = RootManager.shellEscape(isoPath)
|
||||
val fileCheck = RootManager.executeCommand("test -f $safePath && echo exists")
|
||||
if (!fileCheck.success || !fileCheck.output.contains("exists")) {
|
||||
return@withContext MountResult(
|
||||
success = false,
|
||||
@@ -167,9 +168,9 @@ class IsoDriveManager(private val context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
// Build command
|
||||
// Build command with safe path escaping
|
||||
val args = options.toCommandArgs().joinToString(" ")
|
||||
val command = "$binaryPath \"$isoPath\" $args"
|
||||
val command = "$binaryPath $safePath $args"
|
||||
|
||||
val result = RootManager.executeCommand(command)
|
||||
|
||||
|
||||
@@ -456,9 +456,10 @@ private fun DirectoryBrowserDialog(
|
||||
fun loadContents(path: String) {
|
||||
scope.launch {
|
||||
isLoading = true
|
||||
val safePath = RootManager.shellEscape(path)
|
||||
// Load directories
|
||||
val dirResult = RootManager.executeCommand(
|
||||
"find \"$path\" -maxdepth 1 -mindepth 1 -type d 2>/dev/null"
|
||||
"find $safePath -maxdepth 1 -mindepth 1 -type d 2>/dev/null"
|
||||
)
|
||||
val directories = if (dirResult.success && dirResult.output.isNotBlank()) {
|
||||
dirResult.output.lines()
|
||||
@@ -467,8 +468,9 @@ private fun DirectoryBrowserDialog(
|
||||
.filter { !it.substringAfterLast("/").startsWith(".") }
|
||||
.map { dirPath ->
|
||||
// Check if this directory was created by the app (has .isodroiddir marker)
|
||||
val safeDirPath = RootManager.shellEscape(dirPath)
|
||||
val markerCheck = RootManager.executeCommand(
|
||||
"test -f \"$dirPath/.isodroiddir\" && echo 'yes' || echo 'no'"
|
||||
"test -f $safeDirPath/.isodroiddir && echo 'yes' || echo 'no'"
|
||||
)
|
||||
val isDeletable = markerCheck.output.trim() == "yes"
|
||||
BrowserItem(dirPath.substringAfterLast("/"), true, dirPath, isDeletable)
|
||||
@@ -479,7 +481,7 @@ private fun DirectoryBrowserDialog(
|
||||
|
||||
// Load ISO/IMG files
|
||||
val fileResult = RootManager.executeCommand(
|
||||
"find \"$path\" -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||
"find $safePath -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||
)
|
||||
val files = if (fileResult.success && fileResult.output.isNotBlank()) {
|
||||
fileResult.output.lines()
|
||||
@@ -501,9 +503,10 @@ private fun DirectoryBrowserDialog(
|
||||
if (trimmedName.isEmpty()) return@launch
|
||||
|
||||
val newPath = "$currentPath/$trimmedName"
|
||||
RootManager.executeCommand("mkdir -p \"$newPath\"")
|
||||
val safeNewPath = RootManager.shellEscape(newPath)
|
||||
RootManager.executeCommand("mkdir -p $safeNewPath")
|
||||
// Create marker file to indicate this folder was created by the app
|
||||
RootManager.executeCommand("touch \"$newPath/.isodroiddir\"")
|
||||
RootManager.executeCommand("touch $safeNewPath/.isodroiddir")
|
||||
// Auto-navigate into the new folder
|
||||
currentPath = newPath
|
||||
}
|
||||
@@ -511,7 +514,8 @@ private fun DirectoryBrowserDialog(
|
||||
|
||||
fun deleteFolder(path: String) {
|
||||
scope.launch {
|
||||
RootManager.executeCommand("rm -rf \"$path\"")
|
||||
val safePath = RootManager.shellEscape(path)
|
||||
RootManager.executeCommand("rm -rf $safePath")
|
||||
loadContents(currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,9 +205,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!directory.exists()) {
|
||||
RootManager.executeCommand("mkdir -p \"$currentPath\"")
|
||||
val safePath = RootManager.shellEscape(currentPath)
|
||||
RootManager.executeCommand("mkdir -p $safePath")
|
||||
// Create marker file to indicate this folder was created by the app
|
||||
RootManager.executeCommand("touch \"$currentPath/.isodroiddir\"")
|
||||
RootManager.executeCommand("touch $safePath/.isodroiddir")
|
||||
}
|
||||
|
||||
// Try multiple methods to list files
|
||||
@@ -220,8 +221,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private suspend fun loadFilesViaFind(currentPath: String): List<IsoFile>? {
|
||||
// Use find command - more reliable for getting full paths
|
||||
val safePath = RootManager.shellEscape(currentPath)
|
||||
val result = RootManager.executeCommand(
|
||||
"find \"$currentPath\" -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||
"find $safePath -maxdepth 1 -type f \\( -iname '*.iso' -o -iname '*.img' \\) 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!result.success || result.output.isBlank()) return null
|
||||
@@ -231,8 +233,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
.mapNotNull { filePath ->
|
||||
val file = File(filePath.trim())
|
||||
val name = file.name
|
||||
// Get file size via stat
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s \"$filePath\" 2>/dev/null")
|
||||
// Get file size via stat with safe escaping
|
||||
val safeFilePath = RootManager.shellEscape(filePath.trim())
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s $safeFilePath 2>/dev/null")
|
||||
val size = sizeResult.output.trim().toLongOrNull() ?: 0L
|
||||
IsoFile(
|
||||
path = filePath.trim(),
|
||||
@@ -246,8 +249,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private suspend fun loadFilesViaLs(currentPath: String): List<IsoFile>? {
|
||||
// Simple ls command - just get filenames
|
||||
val safePath = RootManager.shellEscape(currentPath)
|
||||
val result = RootManager.executeCommand(
|
||||
"ls \"$currentPath\" 2>/dev/null"
|
||||
"ls $safePath 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!result.success || result.output.isBlank()) return null
|
||||
@@ -259,8 +263,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
.mapNotNull { name ->
|
||||
val filePath = "$currentPath/$name"
|
||||
// Get file size via stat
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s \"$filePath\" 2>/dev/null")
|
||||
// Get file size via stat with safe escaping
|
||||
val safeFilePath = RootManager.shellEscape(filePath)
|
||||
val sizeResult = RootManager.executeCommand("stat -c %s $safeFilePath 2>/dev/null")
|
||||
val size = sizeResult.output.trim().toLongOrNull() ?: 0L
|
||||
IsoFile(
|
||||
path = filePath,
|
||||
@@ -444,13 +449,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val blockSize = 1024 * 1024L // 1MB blocks
|
||||
val totalBlocks = totalBytes / blockSize
|
||||
var writtenBlocks = 0L
|
||||
val safeFilePath = RootManager.shellEscape(filePath)
|
||||
|
||||
// Create file with dd in background, checking for cancellation
|
||||
val result = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
try {
|
||||
// First, create the file with truncate to reserve space indication
|
||||
val createResult = RootManager.executeCommand(
|
||||
"dd if=/dev/zero of=\"$filePath\" bs=1M count=0 seek=$totalBlocks 2>/dev/null"
|
||||
"dd if=/dev/zero of=$safeFilePath bs=1M count=0 seek=$totalBlocks 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!createResult.success) {
|
||||
@@ -461,18 +467,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
while (writtenBlocks < totalBlocks) {
|
||||
if (CreateImgEventBus.isCancelRequested()) {
|
||||
// Clean up partial file
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
RootManager.executeCommand("rm -f $safeFilePath")
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
// Write a chunk (up to 64MB at a time for efficiency)
|
||||
val chunksToWrite = minOf(64, totalBlocks - writtenBlocks)
|
||||
val chunkResult = RootManager.executeCommand(
|
||||
"dd if=/dev/zero of=\"$filePath\" bs=1M count=$chunksToWrite seek=$writtenBlocks conv=notrunc 2>/dev/null"
|
||||
"dd if=/dev/zero of=$safeFilePath bs=1M count=$chunksToWrite seek=$writtenBlocks conv=notrunc 2>/dev/null"
|
||||
)
|
||||
|
||||
if (!chunkResult.success) {
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
RootManager.executeCommand("rm -f $safeFilePath")
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
@@ -484,7 +490,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
RootManager.executeCommand("rm -f \"$filePath\"")
|
||||
RootManager.executeCommand("rm -f $safeFilePath")
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -508,14 +514,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val oldPath = file.path
|
||||
val newPath = "${oldPath.substringBeforeLast("/")}/$newName"
|
||||
|
||||
// Use shell-safe escaping to prevent command injection
|
||||
val safeOldPath = RootManager.shellEscape(oldPath)
|
||||
val safeNewPath = RootManager.shellEscape(newPath)
|
||||
|
||||
// Check if new file already exists
|
||||
val checkResult = RootManager.executeCommand("test -f \"$newPath\" && echo exists")
|
||||
val checkResult = RootManager.executeCommand("test -f $safeNewPath && echo exists")
|
||||
if (checkResult.output.trim() == "exists") {
|
||||
_uiState.update { it.copy(errorMessage = "File already exists: $newName") }
|
||||
return@launch
|
||||
}
|
||||
|
||||
val result = RootManager.executeCommand("mv \"$oldPath\" \"$newPath\"")
|
||||
val result = RootManager.executeCommand("mv $safeOldPath $safeNewPath")
|
||||
if (result.success) {
|
||||
_uiState.update { it.copy(successMessage = "Renamed to $newName") }
|
||||
loadFiles()
|
||||
@@ -527,7 +537,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
fun deleteFile(file: IsoFile) {
|
||||
viewModelScope.launch {
|
||||
val result = RootManager.executeCommand("rm -f \"${file.path}\"")
|
||||
val safePath = RootManager.shellEscape(file.path)
|
||||
val result = RootManager.executeCommand("rm -f $safePath")
|
||||
if (result.success) {
|
||||
_uiState.update { it.copy(successMessage = "Deleted ${file.name}") }
|
||||
loadFiles()
|
||||
|
||||
Reference in New Issue
Block a user