androidapkrootandroid-9.0-pie

Install APK using root, handling new limitations of "/data/local/tmp/" folder


Background

So far, I was able to install APK files using root (within the app), via this code:

pm install -t -f fullPathToApkFile

and if I want to (try to) install to sd-card :

pm install -t -s fullPathToApkFile

The problem

Recently, not sure from which Android version (issue exists on Android P beta, at least), the above method fails, showing me this message:

avc:  denied  { read } for  scontext=u:r:system_server:s0 tcontext=u:object_r:sdcardfs:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:sdcardfs:s0 (from path /storage/emulated/0/Download/FDroid.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/FDroid.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
Exception occurred while executing:
java.lang.IllegalArgumentException: Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
    at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:306)
    at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:884)
    at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:138)
    at android.os.ShellCommand.exec(ShellCommand.java:103)
    at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21125)
    at android.os.Binder.shellCommand(Binder.java:634)
    at android.os.Binder.onTransact(Binder.java:532)
    at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2806)
    at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3841)
    at android.os.Binder.execTransact(Binder.java:731)

This seems to also affect popular apps such as "Titanium backup (pro)", which fails to restore apps.

What I've tried

Looking at what's written, it appears it lacks permission to install APK files that are not in /data/local/tmp/.

So I tried the next things, to see if I can overcome it:

  1. set the access to the file (chmod 777) - didn't help.
  2. grant permissions to my app, of both storage and REQUEST_INSTALL_PACKAGES (using ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent) - didn't help.
  3. create a symlink to the file, so that it will be inside the /data/local/tmp/, using official API:

     Os.symlink(fullPathToApkFile, symLinkFilePath)
    

    This didn't do anything.

  4. create a symlink using this :

     ln -sf $fullPathToApkFile $symLinkFilePath
    

    This partially worked. The file is there, as I can see it in Total Commander app, but when I try to check if it exists there, and when I try to install the APK from there, it fails.

  5. Copy/move (using cp or mv) the file to the /data/local/tmp/ path, and then install from there. This worked, but it has disadvantages: moving is risky because it temporarily hides the original file, and it changes the timestamp of the original file. Copying is bad because of using extra space just for installing (even temporarily) and because it wastes time in doing so.

  6. Copy the APK file, telling it to avoid actual copy (meaning hard link), using this command (taken from here) :

     cp -p -r -l $fullPathToApkFile $tempFileParentPath"
    

    This didn't work. It got me this error:

     cp: /data/local/tmp/test.apk: Cross-device link
    
  7. Checking what happens in other cases of installing apps. When you install via via the IDE, it actually does create the APK file in this special path, but if you install via the Play Store, simple APK install (via Intent) or adb (via PC), it doesn't.

  8. Wrote about this here too: https://issuetracker.google.com/issues/80270303

The questions

  1. Is there any way to overcome the disadvantages of installing the APK using root on this special path? Maybe even avoid handling this path at all?

  2. Why does the OS suddenly require to use this path? Why not use the original path instead, just like in the other methods of installing apps? What do the other methods of installing apps do, that somehow avoids using the spacial path?


Solution

  • One solution, in case you don't mind the moving procedure, is to also save&restore the timestamp of the original file, as such:

        val tempFileParentPath = "/data/local/tmp/"
        val tempFilePath = tempFileParentPath + File(fullPathToApkFile).name
        val apkTimestampTempFile = File(context.cacheDir, "apkTimestamp")
        apkTimestampTempFile.delete()
        apkTimestampTempFile.mkdirs()
        apkTimestampTempFile.createNewFile()
        root.runCommands("touch -r $fullPathToApkFile ${apkTimestampTempFile.absolutePath}")
        root.runCommands("mv $fullPathToApkFile $tempFileParentPath")
        root.runCommands("pm install -t -f $tempFilePath")
        root.runCommands("mv $tempFilePath $fullPathToApkFile")
        root.runCommands("touch -r ${apkTimestampTempFile.absolutePath} $fullPathToApkFile")
        apkTimestampTempFile.delete()
    

    It's still a bit dangerous, but better than copying files...


    EDIT: Google has shown me a nice workaround for this (here) :

    We don't support installation of APKs from random directories on the device. They either need to be installed directly from the host using 'adb install' or you have to stream the contents to install --

    $ cat foo.apk | pm install -S APK_SIZE
    

    While I think this is incorrect that they don't support installing of APK files from random paths (always worked before), the workaround does seem to work. All I needed to change in the code of installing an APK file is as such:

    val length = File(fullPathToApkFile ).length()
    commands.add("cat $fullPathToApkFile | pm install -S $length")
    

    Thing is, now I have some other questions about it :

    1. Does this workaround avoid the moving/copying of the APK into storage, and without affecting the original file ? - seems it does
    2. Will this support any APK file, even large ones? - seems it succeeds in doing it for an APK that takes 433MB, so I think it's safe to use for all sizes.
    3. This is needed only from Android P, right? - so far seems so.
    4. Why does it need the file size as a parameter ? - No idea, but if I remove it, it won't work