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