Android - Foolproof low memory killer

    Android's low memory killer is based on application's out of memory score (oom_adj). The source of truth is in a file in proc filesystem (/proc/pid/oom_adj). Although, this is not really specific to Android, it is effectively used by Activity manager to help determine important applications. A foreground application being the most important application gets a score of 0. Higher the score, lesser is the importance and higher probability of being killed by low memory killer.

  3rd party application developers might look for a way to cheat low memory killer and protect their app from being killed. However, this is not possible, not because the file /proc/pid/oom_adj can't be accessed or written into, instead is because of the sys call corresponding to a write request on the file.

   An application code might look like this,

        String fileName = "/proc/" + android.os.Process.myPid() + "/oom_adj";
        File oomFile = new File(fileName);

         try {
                FileOutputStream os = new FileOutputStream(oomFile);
                final String value = "4";
                os.write(value.getBytes());
                os.close();
         } catch (Exception e) {
                throw new RuntimeException("Unable to update score [" + e.getMessage() + "]");
         }

   Now this code can work as well as fail depending upon the current state of the application or to be more specific the current oom score. If the application is in foreground and if the above code is executed, it would indeed update the oom_score to 4. Upon closer look, it actually updates it from 0 to 4, a potential degrade and higher probability to be killed, a potential suicide.

   So the next alternative is to run this code from a service, which puts the application in background and normally tends to give the process a non-zero oom score. So the tendency would be to set 0 in this case. However, the code fails with a IOException saying access denied (EACCESS).

   How can a FileOutputStream API know about the current foreground status of the application and fail selectivity? Actually it doesn't and the code fails on any attempt to set a lower score than the current score, irrespective of the status of the application.

   Turns out this failure is actually triggered by the sys call implementation of POSIX write() for oom, oom_adj_write. Access is denied when the current process isn't capable of updating and requests a score lesser than the current score. This also explains as to how Android framework gets to update it for applications, just being capable of doing so. :-)


   Even though applications can't directly lower the score, Toast API offers an exploit to lower oom_score documented here.

No comments: