• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 "apexd_image_manager.h"
18 
19 #include <android-base/result.h>
20 #include <android-base/unique_fd.h>
21 #include <sys/sendfile.h>
22 
23 #include <algorithm>
24 #include <chrono>
25 
26 #include "apexd.h"
27 #include "apexd_utils.h"
28 
29 using android::base::borrowed_fd;
30 using android::base::ErrnoError;
31 using android::base::Error;
32 using android::base::Result;
33 using android::base::unique_fd;
34 using namespace std::chrono_literals;
35 
36 namespace android::apex {
37 
38 namespace {
39 
40 ApexImageManager* gImageManager;
41 
SendFile(borrowed_fd dest_fd,const std::string & src_path,size_t size)42 Result<void> SendFile(borrowed_fd dest_fd, const std::string& src_path,
43                       size_t size) {
44   unique_fd src_fd(open(src_path.c_str(), O_RDONLY));
45   if (!src_fd.ok()) {
46     return Error() << "Failed to open " << src_path;
47   }
48   int rc = sendfile(dest_fd.get(), src_fd, nullptr, size);
49   if (rc == -1) {
50     return ErrnoError() << "Failed to sendfile from " << src_path;
51   }
52   return {};
53 }
54 
55 // Find a unique "image" name for the apex name: e.g. com.android.foo_2.apex
AllocateNewName(const std::vector<std::string> & known_names,const std::string & apex_name)56 std::string AllocateNewName(const std::vector<std::string>& known_names,
57                             const std::string& apex_name) {
58   // Note that because fsmgr's ImageManager uses the name as partition name,
59   // the name can't be longer than 36. Let's limit the name up to 26 and reserve
60   // the suffix (e.g "_0000.apex")
61   auto base_name = apex_name.substr(0, 26);
62   auto count = std::ranges::count_if(known_names, [&](const auto& name) {
63     return name.starts_with(base_name);
64   });
65   // Find free slot for the "base_name"
66   for (auto i = 0; i < count; i++) {
67     std::string new_name = base_name + "_" + std::to_string(i) + ".apex";
68     if (std::ranges::find(known_names, new_name) == known_names.end()) {
69       return new_name;
70     }
71   }
72   return base_name + "_" + std::to_string(count) + ".apex";
73 }
74 
75 }  // namespace
76 
ApexImageManager(const std::string & metadata_dir,const std::string & data_dir)77 ApexImageManager::ApexImageManager(const std::string& metadata_dir,
78                                    const std::string& data_dir)
79     : metadata_dir_(metadata_dir),
80       data_dir_(data_dir),
81       fsmgr_(fiemap::ImageManager::Open(metadata_dir, data_dir)) {}
82 
83 // PinApexFiles makes apex_files accessible even before /data is mounted. At a
84 // high-level, it pins those apex files, extract their extents, and save the
85 // extents in metadata_dir_. Later on, regardless of whether /data is mounted or
86 // not, one can use the extents to build dm-liner block devices which will give
87 // direct access to the apex files content, effectively bypassing the filesystem
88 // layer.
89 //
90 // However, in reality, it's slightly more complex than this. Any data stored in
91 // /data is encrypted via dm-default-key. This means that if you construct the
92 // dm-liner block devices directly from the extents of the apex files, you will
93 // get encrypted data when reading the block devices.
94 //
95 // To work around this problem, for each apex file, this function creates a new
96 // file in data_dir_/<name>.img that has the size >= size of the apex
97 // file. That new file is then pinned, and its extents are saved to
98 // metadata_dir_/. Then the function constructs a temporary dm-linear block
99 // device using the extents and copy the content of apex file to the block
100 // device. By doing so, the block device have unencrypted copy of the apex file.
101 //
102 // This comes with a size overhead of extra copies of APEX files and wasted
103 // space due to the file-system specific granularity of pinned files.
104 // (TODO/402256229)
PinApexFiles(std::span<const ApexFile> apex_files)105 Result<std::vector<std::string>> ApexImageManager::PinApexFiles(
106     std::span<const ApexFile> apex_files) {
107   std::vector<std::string> new_images;
108   // On error, clean up new backing files
109   auto guard = base::make_scope_guard([&]() {
110     for (const auto& image : new_images) {
111       fsmgr_->DeleteBackingImage(image);
112     }
113   });
114 
115   for (const auto& apex_file : apex_files) {
116     // Get a unique "image" name from the apex name
117     auto image_name = AllocateNewName(fsmgr_->GetAllBackingImages(),
118                                       apex_file.GetManifest().name());
119 
120     auto apex_path = apex_file.GetPath();
121     auto file_size = OR_RETURN(GetFileSize(apex_path));
122 
123     // Create a pinned file for the apex file using
124     // fiemap::ImageManager::CreateBackingImage() which creates
125     // /data/apex/images/{image_name}.img and saves its extents in
126     // /metadata/apex/images/lp_metadata.
127     auto status = fsmgr_->CreateBackingImage(image_name, file_size, 0);
128     if (!status.is_ok()) {
129       return Error() << "Failed to create a pinned backing file for "
130                      << apex_path;
131     }
132     new_images.emplace_back(image_name);
133 
134     // Now, copy the apex file to the pinned file thru the block device which
135     // bypasseses the filesystem (/data) and encyryption layer (dm-default-key).
136     // MappedDevice::Open() constructs a dm-linear device from the extents of
137     // the pinned file.
138     auto device = fiemap::MappedDevice::Open(fsmgr_.get(), 10s, image_name);
139     if (!device) {
140       return Error() << "Failed to map the image: " << image_name;
141     }
142     OR_RETURN(SendFile(device->fd(), apex_path, file_size));
143   }
144 
145   guard.Disable();
146   return new_images;
147 }
148 
DeleteImage(const std::string & image)149 Result<void> ApexImageManager::DeleteImage(const std::string& image) {
150   if (!fsmgr_->DeleteBackingImage(image)) {
151     return Error() << "Failed to delete backing image: " << image;
152   }
153   return {};
154 }
155 
GetAllImages()156 std::vector<std::string> ApexImageManager::GetAllImages() {
157   return fsmgr_->GetAllBackingImages();
158 }
159 
GetImageManager()160 ApexImageManager* GetImageManager() { return gImageManager; }
161 
InitializeImageManager(ApexImageManager * image_manager)162 void InitializeImageManager(ApexImageManager* image_manager) {
163   gImageManager = image_manager;
164 }
165 
Create(const std::string & metadata_images_dir,const std::string & data_images_dir)166 std::unique_ptr<ApexImageManager> ApexImageManager::Create(
167     const std::string& metadata_images_dir,
168     const std::string& data_images_dir) {
169   return std::unique_ptr<ApexImageManager>(
170       new ApexImageManager(metadata_images_dir, data_images_dir));
171 }
172 
173 }  // namespace android::apex