Files
frameworks_base/services/java/com/android/server/pm/Installer.java
Dianne Hackborn d0c5f515c0 Fix issue #6295373: "Package com.google.android.apps.authenticator2 has...
...mismatched uid: X on disk, Y in settings" errors on Froyo and Gingerbread

Deal more gracefully with the uid changing in three ways:

1. If the uid on disk has become root, then have installd change it to
   the application's uid.  This is to correct a potential case where
   installd was interrupted while linking or unlinking the libs dir,
   during which it temporarily changes the owner of the dir to root
   so that a malicious app can not get in its way.  So if the uid on
   disk has become root, we assume we can safely just change it back
   to the correct uid.

2. When scaning packages at boot, use the same "delete and rebuild data
   directory" code for third party applications as we have for system
   applications.  This allows us to at least end up in a state where the
   app will run, even if its data is lost.

3. But we really don't want to get in to case 2, so if an application
   update is being installed and we find that the uid we now have for
   the app is different than the one on disk, fail the update.  This will
   protect against for example a developer changing the sharedUserId of
   their app and getting into this bad state.

Bug: 6295373
Change-Id: Ic802fdd818ac62449ff3c61d1fff1aa4d4942f39
2012-06-07 16:57:57 -07:00

403 lines
12 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.content.pm.PackageStats;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Slog;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class Installer {
private static final String TAG = "Installer";
private static final boolean LOCAL_DEBUG = false;
InputStream mIn;
OutputStream mOut;
LocalSocket mSocket;
byte buf[] = new byte[1024];
int buflen = 0;
private boolean connect() {
if (mSocket != null) {
return true;
}
Slog.i(TAG, "connecting...");
try {
mSocket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address);
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
} catch (IOException ex) {
disconnect();
return false;
}
return true;
}
private void disconnect() {
Slog.i(TAG, "disconnecting...");
try {
if (mSocket != null)
mSocket.close();
} catch (IOException ex) {
}
try {
if (mIn != null)
mIn.close();
} catch (IOException ex) {
}
try {
if (mOut != null)
mOut.close();
} catch (IOException ex) {
}
mSocket = null;
mIn = null;
mOut = null;
}
private boolean readBytes(byte buffer[], int len) {
int off = 0, count;
if (len < 0)
return false;
while (off != len) {
try {
count = mIn.read(buffer, off, len - off);
if (count <= 0) {
Slog.e(TAG, "read error " + count);
break;
}
off += count;
} catch (IOException ex) {
Slog.e(TAG, "read exception");
break;
}
}
if (LOCAL_DEBUG) {
Slog.i(TAG, "read " + len + " bytes");
}
if (off == len)
return true;
disconnect();
return false;
}
private boolean readReply() {
int len;
buflen = 0;
if (!readBytes(buf, 2))
return false;
len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
if ((len < 1) || (len > 1024)) {
Slog.e(TAG, "invalid reply length (" + len + ")");
disconnect();
return false;
}
if (!readBytes(buf, len))
return false;
buflen = len;
return true;
}
private boolean writeCommand(String _cmd) {
byte[] cmd = _cmd.getBytes();
int len = cmd.length;
if ((len < 1) || (len > 1024))
return false;
buf[0] = (byte) (len & 0xff);
buf[1] = (byte) ((len >> 8) & 0xff);
try {
mOut.write(buf, 0, 2);
mOut.write(cmd, 0, len);
} catch (IOException ex) {
Slog.e(TAG, "write error");
disconnect();
return false;
}
return true;
}
private synchronized String transaction(String cmd) {
if (!connect()) {
Slog.e(TAG, "connection failed");
return "-1";
}
if (!writeCommand(cmd)) {
/*
* If installd died and restarted in the background (unlikely but
* possible) we'll fail on the next write (this one). Try to
* reconnect and write the command one more time before giving up.
*/
Slog.e(TAG, "write command failed? reconnect!");
if (!connect() || !writeCommand(cmd)) {
return "-1";
}
}
if (LOCAL_DEBUG) {
Slog.i(TAG, "send: '" + cmd + "'");
}
if (readReply()) {
String s = new String(buf, 0, buflen);
if (LOCAL_DEBUG) {
Slog.i(TAG, "recv: '" + s + "'");
}
return s;
} else {
if (LOCAL_DEBUG) {
Slog.i(TAG, "fail");
}
return "-1";
}
}
private int execute(String cmd) {
String res = transaction(cmd);
try {
return Integer.parseInt(res);
} catch (NumberFormatException ex) {
return -1;
}
}
public int install(String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
builder.append(' ');
builder.append(gid);
return execute(builder.toString());
}
public int dexopt(String apkPath, int uid, boolean isPublic) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
builder.append(uid);
builder.append(isPublic ? " 1" : " 0");
return execute(builder.toString());
}
public int movedex(String srcPath, String dstPath) {
StringBuilder builder = new StringBuilder("movedex");
builder.append(' ');
builder.append(srcPath);
builder.append(' ');
builder.append(dstPath);
return execute(builder.toString());
}
public int rmdex(String codePath) {
StringBuilder builder = new StringBuilder("rmdex");
builder.append(' ');
builder.append(codePath);
return execute(builder.toString());
}
public int remove(String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return execute(builder.toString());
}
public int rename(String oldname, String newname) {
StringBuilder builder = new StringBuilder("rename");
builder.append(' ');
builder.append(oldname);
builder.append(' ');
builder.append(newname);
return execute(builder.toString());
}
public int fixUid(String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("fixuid");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
builder.append(' ');
builder.append(gid);
return execute(builder.toString());
}
public int deleteCacheFiles(String name) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
builder.append(name);
return execute(builder.toString());
}
public int createUserData(String name, int uid, int userId) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
builder.append(' ');
builder.append(userId);
return execute(builder.toString());
}
public int removeUserDataDirs(int userId) {
StringBuilder builder = new StringBuilder("rmuser");
builder.append(' ');
builder.append(userId);
return execute(builder.toString());
}
public int clearUserData(String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return execute(builder.toString());
}
/**
* Clone all the package data directories from srcUserId to targetUserId. If copyData is true,
* some of the data is also copied, otherwise just empty directories are created with the
* correct access rights.
* @param srcUserId user to copy the data directories from
* @param targetUserId user to copy the data directories to
* @param copyData whether the data itself is to be copied. If false, empty directories are
* created.
* @return success/error code
*/
public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) {
StringBuilder builder = new StringBuilder("cloneuserdata");
builder.append(' ');
builder.append(srcUserId);
builder.append(' ');
builder.append(targetUserId);
builder.append(' ');
builder.append(copyData ? '1' : '0');
return execute(builder.toString());
}
public boolean ping() {
if (execute("ping") < 0) {
return false;
} else {
return true;
}
}
public int freeCache(long freeStorageSize) {
StringBuilder builder = new StringBuilder("freecache");
builder.append(' ');
builder.append(String.valueOf(freeStorageSize));
return execute(builder.toString());
}
/*
* @param packagePathSuffix The name of the path relative to install
* directory. Say if the path name is /data/app/com.test-1.apk, the package
* suffix path will be com.test-1
*/
public int setForwardLockPerm(String packagePathSuffix, int gid) {
StringBuilder builder = new StringBuilder("protect");
builder.append(' ');
builder.append(packagePathSuffix);
builder.append(' ');
builder.append(gid);
return execute(builder.toString());
}
public int getSizeInfo(String pkgName, String apkPath, String fwdLockApkPath,
String asecPath, PackageStats pStats) {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!");
builder.append(' ');
builder.append(asecPath != null ? asecPath : "!");
String s = transaction(builder.toString());
String res[] = s.split(" ");
if ((res == null) || (res.length != 5)) {
return -1;
}
try {
pStats.codeSize = Long.parseLong(res[1]);
pStats.dataSize = Long.parseLong(res[2]);
pStats.cacheSize = Long.parseLong(res[3]);
pStats.externalCodeSize = Long.parseLong(res[4]);
return Integer.parseInt(res[0]);
} catch (NumberFormatException e) {
return -1;
}
}
public int moveFiles() {
return execute("movefiles");
}
public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) {
if (dataPath == null) {
Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
return -1;
} else if (nativeLibPath == null) {
Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null");
return -1;
}
StringBuilder builder = new StringBuilder("linklib ");
builder.append(dataPath);
builder.append(' ');
builder.append(nativeLibPath);
return execute(builder.toString());
}
public int unlinkNativeLibraryDirectory(String dataPath) {
if (dataPath == null) {
Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
return -1;
}
StringBuilder builder = new StringBuilder("unlinklib ");
builder.append(dataPath);
return execute(builder.toString());
}
}