简体   繁体   中英

Java runtime.exec user input race condition

I want my app to be able to use a global su instance. I have code that does that, but I have encountered a race condition, I believe.

I am storing some variables for su like so:

public static List<Object> rootObjects = Collections.synchronizedList(new ArrayList<>());

protected void onCreate(Bundle savedInstanceState) {
    ...
    if(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getBoolean("use_su", false) && rootObjects.isEmpty())
    {
        try {
            Process process = Runtime.getRuntime().exec("su");
            rootObjects.add(process);
            InputStream inputStream = new DataInputStream(process.getInputStream());
            rootObjects.add(inputStream);
            OutputStream outputStream = new DataOutputStream(process.getOutputStream());
            rootObjects.add(outputStream);
        } catch (IOException e) {
            Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
        }
        finally {
            synchronized (rootObjects) {
                rootObjects.notifyAll();
            }
        }
    }
}

and using them like so:

byte[] getPrivateKeyAsSuperUser() {
    byte[] data = null;
    DataInputStream inputStream = null;
    DataOutputStream outputStream = null;

    if(MainActivity.rootObjects.size() != 3)
        synchronized (MainActivity.rootObjects)
        {
            try {
                MainActivity.rootObjects.wait();
            } catch (InterruptedException e) {
                Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
            }
        }

    for(Object rootObj : MainActivity.rootObjects)
    {
        if(rootObj instanceof DataInputStream)
            inputStream = (DataInputStream) rootObj;
        else if(rootObj instanceof DataOutputStream)
            outputStream = (DataOutputStream) rootObj;
    }
    try {
        outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
        outputStream.flush();
        data = readStream(inputStream);
    } catch (IOException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    return data;
}

private byte[] readStream(InputStream stream) {
    byte[] data = null;
    try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte buff[] = new byte[1024];
        int count = 0;

        while (stream.available() != 0 && (count = stream.read(buff)) != -1) {
            bos.write(buff, 0, count);
        }
        data = bos.toByteArray();
        //System.out.println(new String(data));
    } catch (IOException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    return data;
}

But it does not wait like I expect, and I instantly receive a Toast that the returned private key is not valid with my sanity check (It's probably null).

The code works if I let Process finish initializing, but I'd like the program to do that for me.

I've tried some other synchronization techniques such as locks, but apparently as soon as you know if an object has a lock your info is stale.

What is the best thread safe approach to have the caller of getPrivateKeyAsSuperUser() wait if Process is not initialized properly?

EDIT:

I would like to add that through some debugging, I have found that I do not want be waiting for Process to initialize (because what I have DOES that), but rather, that the shell spawned by su is valid to accept further commands. I suppose I could have a thread pipe something like echo DONE and loop until I get DONE back, but that seems like that would waste CPU horsepower. If someone could lend some knowledge on the subject, I would be extremely grateful.

You're attempting the singleton pattern here. I'm not sure why you want to store these objects in a list. The most sensible way to store them is in an object that you guarantee to create one instance of. There are a few ways you could do this. I think in your case the following would work

public class SuProcessHolder {
    // store the state of the process here - this would be your Process and streams as above
    // these should be non-static members of the class

    // this would be the singleton instance you'll use - it will be constructed once 
    // on first use
    private static SuProcessHolder singletonInstance = new SuProcessHolder();

    public SuProcessHolder() {
        // put your construction code in here to create an SU process
    }


    // this will access your SU process
    public static SuProcessHolder getInstance() { return singletonInstance; }
}

Then, wherever you need your SU process, just call

SuProcessHolder.getInstance()

and it will be there like a Michael Jackson song.

I have solved it. I did end up having to echo and check for done, but I have done it without a loop, or sleeping in my thread, so it will fire as soon as it can, without hogging the CPU. The concurrent class I was looking for was CountDownLatch as well.

The assignment look like this:

process = Runtime.getRuntime().exec("su");
outputStream = new DataOutputStream(process.getOutputStream());
outputStream.writeBytes("echo DONE\n");
outputStream.flush();
inputStream = new DataInputStream(process.getInputStream());
byte[] buff = new byte[4];
inputStream.read(buff);
if(new String(buff).equals("DONE"));
     MainActivity.rootLatch.countDown();

and getPrivateKeyAsSuperUser() became:

byte[] getPrivateKeyAsSuperUser() {

    byte[] data = null;
    try {
        MainActivity.rootLatch.await();
    } catch (InterruptedException e) {
        Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
    }
    Su su = Su.getStaticInstance();
        try {
            su.outputStream.writeBytes(String.format("cat \"%s\"\n", sshPrivateKey.getAbsolutePath()));
            su.outputStream.flush();
            data = readStream(su.inputStream);
        } catch (IOException e) {
            Log.d(MainActivity.mainActivity.getPackageName(), e.getLocalizedMessage());
        }
    return data;

}

Although, this feels slightly sloppy, I may end up posting this on Code Review.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM