Add Installd IPC to compute the SHA256 of a seconday dex file.
(cherry picked from commit 753dc71734927f86c277991be9326cb9a82202b1)
Bug: 63927552
Test: Exercised manually. Added unit tests in installd_service_test.
Merged-In: If9df7a88f3a3039aab69ed5f200d14cb19794cb3
Change-Id: If9df7a88f3a3039aab69ed5f200d14cb19794cb3
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index 7d1537a..90c7da4 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -18,6 +18,7 @@
shared_libs: [
"libbase",
"libbinder",
+ "libcrypto",
"libcutils",
"liblog",
"liblogwrap",
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk
index 1d21b3c..b3dc5ec 100644
--- a/cmds/installd/Android.mk
+++ b/cmds/installd/Android.mk
@@ -27,6 +27,7 @@
LOCAL_HEADER_LIBRARIES := dex2oat_headers
LOCAL_SHARED_LIBRARIES := \
libbase \
+ libcrypto \
libcutils \
liblog \
liblogwrap \
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index b0661c5..fab24e5 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -2379,6 +2379,22 @@
return result ? ok() : error();
}
+binder::Status InstalldNativeService::hashSecondaryDexFile(
+ const std::string& dexPath, const std::string& packageName, int32_t uid,
+ const std::unique_ptr<std::string>& volumeUuid, int32_t storageFlag,
+ std::vector<uint8_t>* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(volumeUuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+
+ // mLock is not taken here since we will never modify the file system.
+ // If a file is modified just as we are reading it this may result in an
+ // anomalous hash, but that's ok.
+ bool result = android::installd::hash_secondary_dex_file(
+ dexPath, packageName, uid, volumeUuid, storageFlag, _aidl_return);
+ return result ? ok() : error();
+}
+
binder::Status InstalldNativeService::invalidateMounts() {
ENFORCE_UID(AID_SYSTEM);
std::lock_guard<std::recursive_mutex> lock(mMountsLock);
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 93b59ed..cef62cd 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -123,6 +123,9 @@
binder::Status reconcileSecondaryDexFile(const std::string& dexPath,
const std::string& packageName, int32_t uid, const std::vector<std::string>& isa,
const std::unique_ptr<std::string>& volumeUuid, int32_t storage_flag, bool* _aidl_return);
+ binder::Status hashSecondaryDexFile(const std::string& dexPath,
+ const std::string& packageName, int32_t uid, const std::unique_ptr<std::string>& volumeUuid,
+ int32_t storageFlag, std::vector<uint8_t>* _aidl_return);
binder::Status invalidateMounts();
binder::Status isQuotaSupported(const std::unique_ptr<std::string>& volumeUuid,
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 2c9c6bd..c819e98 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -87,6 +87,9 @@
int uid, in @utf8InCpp String[] isas, @nullable @utf8InCpp String volume_uuid,
int storage_flag);
+ byte[] hashSecondaryDexFile(@utf8InCpp String dexPath, @utf8InCpp String pkgName,
+ int uid, @nullable @utf8InCpp String volumeUuid, int storageFlag);
+
void invalidateMounts();
boolean isQuotaSupported(@nullable @utf8InCpp String uuid);
}
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index a2f74ba..8402092 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "installed"
+#include <array>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -27,6 +28,7 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
@@ -36,6 +38,7 @@
#include <cutils/properties.h>
#include <cutils/sched_policy.h>
#include <log/log.h> // TODO: Move everything to base/logging.
+#include <openssl/sha.h>
#include <private/android_filesystem_config.h>
#include <selinux/android.h>
#include <system/thread_defs.h>
@@ -46,8 +49,10 @@
#include "otapreopt_utils.h"
#include "utils.h"
-using android::base::StringPrintf;
using android::base::EndsWith;
+using android::base::ReadFully;
+using android::base::StringPrintf;
+using android::base::WriteFully;
using android::base::unique_fd;
namespace android {
@@ -2081,6 +2086,90 @@
}
}
+// Compute and return the hash (SHA-256) of the secondary dex file at dex_path.
+// Returns true if all parameters are valid and the hash successfully computed and stored in
+// out_secondary_dex_hash.
+// Also returns true with an empty hash if the file does not currently exist or is not accessible to
+// the app.
+// For any other errors (e.g. if any of the parameters are invalid) returns false.
+bool hash_secondary_dex_file(const std::string& dex_path, const std::string& pkgname, int uid,
+ const std::unique_ptr<std::string>& volume_uuid, int storage_flag,
+ std::vector<uint8_t>* out_secondary_dex_hash) {
+ out_secondary_dex_hash->clear();
+
+ const char* volume_uuid_cstr = volume_uuid == nullptr ? nullptr : volume_uuid->c_str();
+
+ if (storage_flag != FLAG_STORAGE_CE && storage_flag != FLAG_STORAGE_DE) {
+ LOG(ERROR) << "hash_secondary_dex_file called with invalid storage_flag: "
+ << storage_flag;
+ return false;
+ }
+
+ // Pipe to get the hash result back from our child process.
+ unique_fd pipe_read, pipe_write;
+ if (!Pipe(&pipe_read, &pipe_write)) {
+ PLOG(ERROR) << "Failed to create pipe";
+ return false;
+ }
+
+ // Fork so that actual access to the files is done in the app's own UID, to ensure we only
+ // access data the app itself can access.
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child -- drop privileges before continuing
+ drop_capabilities(uid);
+ pipe_read.reset();
+
+ if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid_cstr, uid, storage_flag)) {
+ LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+ _exit(1);
+ }
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(dex_path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
+ if (fd == -1) {
+ if (errno == EACCES || errno == ENOENT) {
+ // Not treated as an error.
+ _exit(0);
+ }
+ PLOG(ERROR) << "Failed to open secondary dex " << dex_path;
+ _exit(1);
+ }
+
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+
+ std::vector<uint8_t> buffer(65536);
+ while (true) {
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer.data(), buffer.size()));
+ if (bytes_read == 0) {
+ break;
+ } else if (bytes_read == -1) {
+ PLOG(ERROR) << "Failed to read secondary dex " << dex_path;
+ _exit(1);
+ }
+
+ SHA256_Update(&ctx, buffer.data(), bytes_read);
+ }
+
+ std::array<uint8_t, SHA256_DIGEST_LENGTH> hash;
+ SHA256_Final(hash.data(), &ctx);
+ if (!WriteFully(pipe_write, hash.data(), hash.size())) {
+ _exit(1);
+ }
+
+ _exit(0);
+ }
+
+ // parent
+ pipe_write.reset();
+
+ out_secondary_dex_hash->resize(SHA256_DIGEST_LENGTH);
+ if (!ReadFully(pipe_read, out_secondary_dex_hash->data(), out_secondary_dex_hash->size())) {
+ out_secondary_dex_hash->clear();
+ }
+ return wait_child(pid) == 0;
+}
+
// Helper for move_ab, so that we can have common failure-case cleanup.
static bool unlink_and_rename(const char* from, const char* to) {
// Check whether "from" exists, and if so whether it's regular. If it is, unlink. Otherwise,
diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h
index 4923a43..b1506c3 100644
--- a/cmds/installd/dexopt.h
+++ b/cmds/installd/dexopt.h
@@ -75,6 +75,10 @@
const std::unique_ptr<std::string>& volumeUuid, int storage_flag,
/*out*/bool* out_secondary_dex_exists);
+bool hash_secondary_dex_file(const std::string& dex_path,
+ const std::string& pkgname, int uid, const std::unique_ptr<std::string>& volume_uuid,
+ int storage_flag, std::vector<uint8_t>* out_secondary_dex_hash);
+
int dexopt(const char *apk_path, uid_t uid, const char *pkgName, const char *instruction_set,
int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
const char* volume_uuid, const char* class_loader_context, const char* se_info,
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index 1a22992..47346fb 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -24,6 +24,7 @@
shared_libs: [
"libbase",
"libbinder",
+ "libcrypto",
"libcutils",
"libselinux",
"libutils",
@@ -44,6 +45,7 @@
shared_libs: [
"libbase",
"libbinder",
+ "libcrypto",
"libcutils",
"libselinux",
"libutils",
@@ -64,6 +66,7 @@
shared_libs: [
"libbase",
"libbinder",
+ "libcrypto",
"libcutils",
"libselinux",
"libutils",
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index ca812bd..a5af5d7 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <sys/statvfs.h>
@@ -56,16 +57,19 @@
return create_cache_path_default(path, src, instruction_set);
}
+static std::string get_full_path(const char* path) {
+ return StringPrintf("/data/local/tmp/user/0/%s", path);
+}
+
static void mkdir(const char* path, uid_t owner, gid_t group, mode_t mode) {
- const char* fullPath = StringPrintf("/data/local/tmp/user/0/%s", path).c_str();
- ::mkdir(fullPath, mode);
- ::chown(fullPath, owner, group);
- ::chmod(fullPath, mode);
+ const std::string fullPath = get_full_path(path);
+ ::mkdir(fullPath.c_str(), mode);
+ ::chown(fullPath.c_str(), owner, group);
+ ::chmod(fullPath.c_str(), mode);
}
static void touch(const char* path, uid_t owner, gid_t group, mode_t mode) {
- int fd = ::open(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(),
- O_RDWR | O_CREAT, mode);
+ int fd = ::open(get_full_path(path).c_str(), O_RDWR | O_CREAT, mode);
::fchown(fd, owner, group);
::fchmod(fd, mode);
::close(fd);
@@ -73,13 +77,13 @@
static int stat_gid(const char* path) {
struct stat buf;
- ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+ ::stat(get_full_path(path).c_str(), &buf);
return buf.st_gid;
}
static int stat_mode(const char* path) {
struct stat buf;
- ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+ ::stat(get_full_path(path).c_str(), &buf);
return buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISGID);
}
@@ -149,6 +153,69 @@
EXPECT_EQ(10000, stat_gid("com.example/bar/file"));
}
+TEST_F(ServiceTest, HashSecondaryDex) {
+ LOG(INFO) << "HashSecondaryDex";
+
+ mkdir("com.example", 10000, 10000, 0700);
+ mkdir("com.example/foo", 10000, 10000, 0700);
+ touch("com.example/foo/file", 10000, 20000, 0700);
+
+ std::vector<uint8_t> result;
+ std::string dexPath = get_full_path("com.example/foo/file");
+ EXPECT_TRUE(service->hashSecondaryDexFile(
+ dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+ EXPECT_EQ(result.size(), 32U);
+
+ std::ostringstream output;
+ output << std::hex << std::setfill('0');
+ for (auto b : result) {
+ output << std::setw(2) << +b;
+ }
+
+ // This is the SHA256 of an empty string (sha256sum /dev/null)
+ EXPECT_EQ(output.str(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_NoSuch) {
+ LOG(INFO) << "HashSecondaryDex_NoSuch";
+
+ std::vector<uint8_t> result;
+ std::string dexPath = get_full_path("com.example/foo/file");
+ EXPECT_TRUE(service->hashSecondaryDexFile(
+ dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+ EXPECT_EQ(result.size(), 0U);
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_Unreadable) {
+ LOG(INFO) << "HashSecondaryDex_Unreadable";
+
+ mkdir("com.example", 10000, 10000, 0700);
+ mkdir("com.example/foo", 10000, 10000, 0700);
+ touch("com.example/foo/file", 10000, 20000, 0300);
+
+ std::vector<uint8_t> result;
+ std::string dexPath = get_full_path("com.example/foo/file");
+ EXPECT_TRUE(service->hashSecondaryDexFile(
+ dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+ EXPECT_EQ(result.size(), 0U);
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_WrongApp) {
+ LOG(INFO) << "HashSecondaryDex_WrongApp";
+
+ mkdir("com.example", 10000, 10000, 0700);
+ mkdir("com.example/foo", 10000, 10000, 0700);
+ touch("com.example/foo/file", 10000, 20000, 0700);
+
+ std::vector<uint8_t> result;
+ std::string dexPath = get_full_path("com.example/foo/file");
+ EXPECT_FALSE(service->hashSecondaryDexFile(
+ dexPath, "com.wrong", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+}
+
TEST_F(ServiceTest, CalculateOat) {
char buf[PKG_PATH_MAX];