Home | History | Annotate | Download | only in installd
      1 /*
      2  ** Copyright 2016, The Android Open Source Project
      3  **
      4  ** Licensed under the Apache License, Version 2.0 (the "License");
      5  ** you may not use this file except in compliance with the License.
      6  ** You may obtain a copy of the License at
      7  **
      8  **     http://www.apache.org/licenses/LICENSE-2.0
      9  **
     10  ** Unless required by applicable law or agreed to in writing, software
     11  ** distributed under the License is distributed on an "AS IS" BASIS,
     12  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  ** See the License for the specific language governing permissions and
     14  ** limitations under the License.
     15  */
     16 
     17 #include <fcntl.h>
     18 #include <linux/unistd.h>
     19 #include <sys/mount.h>
     20 #include <sys/stat.h>
     21 #include <sys/wait.h>
     22 
     23 #include <sstream>
     24 
     25 #include <android-base/logging.h>
     26 #include <android-base/macros.h>
     27 #include <android-base/stringprintf.h>
     28 #include <libdm/dm.h>
     29 #include <selinux/android.h>
     30 
     31 #include <apexd.h>
     32 
     33 #include "installd_constants.h"
     34 #include "otapreopt_utils.h"
     35 
     36 #ifndef LOG_TAG
     37 #define LOG_TAG "otapreopt"
     38 #endif
     39 
     40 using android::base::StringPrintf;
     41 
     42 namespace android {
     43 namespace installd {
     44 
     45 static void CloseDescriptor(int fd) {
     46     if (fd >= 0) {
     47         int result = close(fd);
     48         UNUSED(result);  // Ignore result. Printing to logcat will open a new descriptor
     49                          // that we do *not* want.
     50     }
     51 }
     52 
     53 static void CloseDescriptor(const char* descriptor_string) {
     54     int fd = -1;
     55     std::istringstream stream(descriptor_string);
     56     stream >> fd;
     57     if (!stream.fail()) {
     58         CloseDescriptor(fd);
     59     }
     60 }
     61 
     62 static std::vector<apex::ApexFile> ActivateApexPackages() {
     63     // The logic here is (partially) copied and adapted from
     64     // system/apex/apexd/apexd_main.cpp.
     65     //
     66     // Only scan the APEX directory under /system (within the chroot dir).
     67     apex::scanPackagesDirAndActivate(apex::kApexPackageSystemDir);
     68     return apex::getActivePackages();
     69 }
     70 
     71 static void DeactivateApexPackages(const std::vector<apex::ApexFile>& active_packages) {
     72     for (const apex::ApexFile& apex_file : active_packages) {
     73         const std::string& package_path = apex_file.GetPath();
     74         apex::Status status = apex::deactivatePackage(package_path);
     75         if (!status.Ok()) {
     76             LOG(ERROR) << "Failed to deactivate " << package_path << ": " << status.ErrorMessage();
     77         }
     78     }
     79 }
     80 
     81 static void TryExtraMount(const char* name, const char* slot, const char* target) {
     82     std::string partition_name = StringPrintf("%s%s", name, slot);
     83 
     84     // See whether update_engine mounted a logical partition.
     85     {
     86         auto& dm = dm::DeviceMapper::Instance();
     87         if (dm.GetState(partition_name) != dm::DmDeviceState::INVALID) {
     88             std::string path;
     89             if (dm.GetDmDevicePathByName(partition_name, &path)) {
     90                 int mount_result = mount(path.c_str(),
     91                                          target,
     92                                          "ext4",
     93                                          MS_RDONLY,
     94                                          /* data */ nullptr);
     95                 if (mount_result == 0) {
     96                     return;
     97                 }
     98             }
     99         }
    100     }
    101 
    102     // Fall back and attempt a direct mount.
    103     std::string block_device = StringPrintf("/dev/block/by-name/%s", partition_name.c_str());
    104     int mount_result = mount(block_device.c_str(),
    105                              target,
    106                              "ext4",
    107                              MS_RDONLY,
    108                              /* data */ nullptr);
    109     UNUSED(mount_result);
    110 }
    111 
    112 // Entry for otapreopt_chroot. Expected parameters are:
    113 //   [cmd] [status-fd] [target-slot] "dexopt" [dexopt-params]
    114 // The file descriptor denoted by status-fd will be closed. The rest of the parameters will
    115 // be passed on to otapreopt in the chroot.
    116 static int otapreopt_chroot(const int argc, char **arg) {
    117     // Validate arguments
    118     // We need the command, status channel and target slot, at a minimum.
    119     if(argc < 3) {
    120         PLOG(ERROR) << "Not enough arguments.";
    121         exit(208);
    122     }
    123     // Close all file descriptors. They are coming from the caller, we do not want to pass them
    124     // on across our fork/exec into a different domain.
    125     // 1) Default descriptors.
    126     CloseDescriptor(STDIN_FILENO);
    127     CloseDescriptor(STDOUT_FILENO);
    128     CloseDescriptor(STDERR_FILENO);
    129     // 2) The status channel.
    130     CloseDescriptor(arg[1]);
    131 
    132     // We need to run the otapreopt tool from the postinstall partition. As such, set up a
    133     // mount namespace and change root.
    134 
    135     // Create our own mount namespace.
    136     if (unshare(CLONE_NEWNS) != 0) {
    137         PLOG(ERROR) << "Failed to unshare() for otapreopt.";
    138         exit(200);
    139     }
    140 
    141     // Make postinstall private, so that our changes don't propagate.
    142     if (mount("", "/postinstall", nullptr, MS_PRIVATE, nullptr) != 0) {
    143         PLOG(ERROR) << "Failed to mount private.";
    144         exit(201);
    145     }
    146 
    147     // Bind mount necessary directories.
    148     constexpr const char* kBindMounts[] = {
    149             "/data", "/dev", "/proc", "/sys"
    150     };
    151     for (size_t i = 0; i < arraysize(kBindMounts); ++i) {
    152         std::string trg = StringPrintf("/postinstall%s", kBindMounts[i]);
    153         if (mount(kBindMounts[i], trg.c_str(), nullptr, MS_BIND, nullptr) != 0) {
    154             PLOG(ERROR) << "Failed to bind-mount " << kBindMounts[i];
    155             exit(202);
    156         }
    157     }
    158 
    159     // Try to mount the vendor partition. update_engine doesn't do this for us, but we
    160     // want it for vendor APKs.
    161     // Notes:
    162     //  1) We pretty much guess a name here and hope to find the partition by name.
    163     //     It is just as complicated and brittle to scan /proc/mounts. But this requires
    164     //     validating the target-slot so as not to try to mount some totally random path.
    165     //  2) We're in a mount namespace here, so when we die, this will be cleaned up.
    166     //  3) Ignore errors. Printing anything at this stage will open a file descriptor
    167     //     for logging.
    168     if (!ValidateTargetSlotSuffix(arg[2])) {
    169         LOG(ERROR) << "Target slot suffix not legal: " << arg[2];
    170         exit(207);
    171     }
    172     TryExtraMount("vendor", arg[2], "/postinstall/vendor");
    173 
    174     // Try to mount the product partition. update_engine doesn't do this for us, but we
    175     // want it for product APKs. Same notes as vendor above.
    176     TryExtraMount("product", arg[2], "/postinstall/product");
    177 
    178     // Setup APEX mount point and its security context.
    179     static constexpr const char* kPostinstallApexDir = "/postinstall/apex";
    180     // The following logic is similar to the one in system/core/rootdir/init.rc:
    181     //
    182     //   mount tmpfs tmpfs /apex nodev noexec nosuid
    183     //   chmod 0755 /apex
    184     //   chown root root /apex
    185     //   restorecon /apex
    186     //
    187     // except we perform the `restorecon` step just after mounting the tmpfs
    188     // filesystem in /postinstall/apex, so that this directory is correctly
    189     // labeled (with type `postinstall_apex_mnt_dir`) and may be manipulated in
    190     // following operations (`chmod`, `chown`, etc.) following policies
    191     // restricted to `postinstall_apex_mnt_dir`:
    192     //
    193     //   mount tmpfs tmpfs /postinstall/apex nodev noexec nosuid
    194     //   restorecon /postinstall/apex
    195     //   chmod 0755 /postinstall/apex
    196     //   chown root root /postinstall/apex
    197     //
    198     if (mount("tmpfs", kPostinstallApexDir, "tmpfs", MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr)
    199         != 0) {
    200         PLOG(ERROR) << "Failed to mount tmpfs in " << kPostinstallApexDir;
    201         exit(209);
    202     }
    203     if (selinux_android_restorecon(kPostinstallApexDir, 0) < 0) {
    204         PLOG(ERROR) << "Failed to restorecon " << kPostinstallApexDir;
    205         exit(214);
    206     }
    207     if (chmod(kPostinstallApexDir, 0755) != 0) {
    208         PLOG(ERROR) << "Failed to chmod " << kPostinstallApexDir << " to 0755";
    209         exit(210);
    210     }
    211     if (chown(kPostinstallApexDir, 0, 0) != 0) {
    212         PLOG(ERROR) << "Failed to chown " << kPostinstallApexDir << " to root:root";
    213         exit(211);
    214     }
    215 
    216     // Chdir into /postinstall.
    217     if (chdir("/postinstall") != 0) {
    218         PLOG(ERROR) << "Unable to chdir into /postinstall.";
    219         exit(203);
    220     }
    221 
    222     // Make /postinstall the root in our mount namespace.
    223     if (chroot(".")  != 0) {
    224         PLOG(ERROR) << "Failed to chroot";
    225         exit(204);
    226     }
    227 
    228     if (chdir("/") != 0) {
    229         PLOG(ERROR) << "Unable to chdir into /.";
    230         exit(205);
    231     }
    232 
    233     // Try to mount APEX packages in "/apex" in the chroot dir. We need at least
    234     // the Android Runtime APEX, as it is required by otapreopt to run dex2oat.
    235     std::vector<apex::ApexFile> active_packages = ActivateApexPackages();
    236 
    237     // Now go on and run otapreopt.
    238 
    239     // Incoming:  cmd + status-fd + target-slot + cmd...      | Incoming | = argc
    240     // Outgoing:  cmd             + target-slot + cmd...      | Outgoing | = argc - 1
    241     std::vector<std::string> cmd;
    242     cmd.reserve(argc);
    243     cmd.push_back("/system/bin/otapreopt");
    244 
    245     // The first parameter is the status file descriptor, skip.
    246     for (size_t i = 2; i < static_cast<size_t>(argc); ++i) {
    247         cmd.push_back(arg[i]);
    248     }
    249 
    250     // Fork and execute otapreopt in its own process.
    251     std::string error_msg;
    252     bool exec_result = Exec(cmd, &error_msg);
    253     if (!exec_result) {
    254         LOG(ERROR) << "Running otapreopt failed: " << error_msg;
    255     }
    256 
    257     // Tear down the work down by the apexd logic. (i.e. deactivate packages).
    258     DeactivateApexPackages(active_packages);
    259 
    260     if (!exec_result) {
    261         exit(213);
    262     }
    263 
    264     return 0;
    265 }
    266 
    267 }  // namespace installd
    268 }  // namespace android
    269 
    270 int main(const int argc, char *argv[]) {
    271     return android::installd::otapreopt_chroot(argc, argv);
    272 }
    273