• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 <charconv>
18 #include <filesystem>
19 #include <map>
20 #include <span>
21 #include <string>
22 
23 #include <fcntl.h>
24 #include <linux/fs.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 
29 #include "android-base/errors.h"
30 #include <android-base/file.h>
31 #include <android-base/logging.h>
32 #include <android-base/result.h>
33 #include <android-base/unique_fd.h>
34 #include <asm/byteorder.h>
35 #include <libfsverity.h>
36 #include <linux/fsverity.h>
37 
38 #define FS_VERITY_MAX_DIGEST_SIZE 64
39 
40 using android::base::ErrnoError;
41 using android::base::Error;
42 using android::base::Result;
43 using android::base::unique_fd;
44 
45 static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
46 static const char* kFsVerityProcPath = "/proc/sys/fs/verity";
47 
SupportsFsVerity()48 bool SupportsFsVerity() {
49     return access(kFsVerityProcPath, F_OK) == 0;
50 }
51 
toHex(std::span<const uint8_t> data)52 static std::string toHex(std::span<const uint8_t> data) {
53     std::stringstream ss;
54     for (auto it = data.begin(); it != data.end(); ++it) {
55         ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
56     }
57     return ss.str();
58 }
59 
read_callback(void * file,void * buf,size_t count)60 static int read_callback(void* file, void* buf, size_t count) {
61     int* fd = (int*)file;
62     if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO;
63     return 0;
64 }
65 
createDigest(int fd)66 static Result<std::vector<uint8_t>> createDigest(int fd) {
67     struct stat filestat;
68     int ret = fstat(fd, &filestat);
69     if (ret < 0) {
70         return ErrnoError() << "Failed to fstat";
71     }
72     struct libfsverity_merkle_tree_params params = {
73         .version = 1,
74         .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
75         .file_size = static_cast<uint64_t>(filestat.st_size),
76         .block_size = 4096,
77     };
78 
79     struct libfsverity_digest* digest;
80     ret = libfsverity_compute_digest(&fd, &read_callback, &params, &digest);
81     if (ret < 0) {
82         return ErrnoError() << "Failed to compute fs-verity digest";
83     }
84     int expected_digest_size = libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256);
85     if (digest->digest_size != expected_digest_size) {
86         return Error() << "Digest does not have expected size: " << expected_digest_size
87                        << " actual: " << digest->digest_size;
88     }
89     std::vector<uint8_t> digestVector(&digest->digest[0], &digest->digest[expected_digest_size]);
90     free(digest);
91     return digestVector;
92 }
93 
createDigest(const std::string & path)94 Result<std::vector<uint8_t>> createDigest(const std::string& path) {
95     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
96     if (!fd.ok()) {
97         return ErrnoError() << "Unable to open";
98     }
99     return createDigest(fd.get());
100 }
101 
102 namespace {
103 template <typename T> struct DeleteAsPODArray {
operator ()__anon02c1e75c0111::DeleteAsPODArray104     void operator()(T* x) {
105         if (x) {
106             x->~T();
107             delete[](uint8_t*) x;
108         }
109     }
110 };
111 
112 template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>;
113 
114 template <typename T>
makeUniqueWithTrailingData(size_t trailing_data_size)115 static trailing_unique_ptr<T> makeUniqueWithTrailingData(size_t trailing_data_size) {
116     uint8_t* memory = new uint8_t[sizeof(T) + trailing_data_size];
117     T* ptr = new (memory) T;
118     return trailing_unique_ptr<T>{ptr};
119 }
120 
measureFsVerity(int fd)121 static Result<std::string> measureFsVerity(int fd) {
122     auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
123     d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
124 
125     if (ioctl(fd, FS_IOC_MEASURE_VERITY, d.get()) != 0) {
126         if (errno == ENODATA) {
127             return Error() << "File is not in fs-verity";
128         } else {
129             return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
130         }
131     }
132 
133     return toHex({&d->digest[0], &d->digest[d->digest_size]});
134 }
135 
measureFsVerity(const std::string & path)136 static Result<std::string> measureFsVerity(const std::string& path) {
137     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
138     if (!fd.ok()) {
139         return ErrnoError() << "Failed to open " << path;
140     }
141 
142     return measureFsVerity(fd.get());
143 }
144 
145 }  // namespace
146 
enableFsVerity(int fd)147 static Result<void> enableFsVerity(int fd) {
148     struct fsverity_enable_arg arg = {.version = 1};
149 
150     arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
151     arg.block_size = 4096;
152 
153     int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
154 
155     if (ret != 0) {
156         return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY";
157     }
158 
159     return {};
160 }
161 
enableFsVerity(const std::string & path)162 Result<void> enableFsVerity(const std::string& path) {
163     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
164     if (!fd.ok()) {
165         return Error() << "Can't open " << path;
166     }
167 
168     return enableFsVerity(fd.get());
169 }
170 
isFileInVerity(int fd)171 static Result<bool> isFileInVerity(int fd) {
172     unsigned int flags;
173     if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0) {
174         return ErrnoError() << "ioctl(FS_IOC_GETFLAGS) failed";
175     }
176     return (flags & FS_VERITY_FL) != 0;
177 }
178 
addFilesToVerityRecursive(const std::string & path)179 Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path) {
180     std::map<std::string, std::string> digests;
181 
182     std::error_code ec;
183     auto it = std::filesystem::recursive_directory_iterator(path, ec);
184     for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
185         if (it->is_regular_file()) {
186             unique_fd fd(TEMP_FAILURE_RETRY(open(it->path().c_str(), O_RDONLY | O_CLOEXEC)));
187             if (!fd.ok()) {
188                 return ErrnoError() << "Failed to open " << path;
189             }
190             auto enabled = OR_RETURN(isFileInVerity(fd));
191             if (!enabled) {
192                 LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
193                 OR_RETURN(enableFsVerity(fd));
194             } else {
195                 LOG(INFO) << it->path() << " was already in fs-verity.";
196             }
197             auto digest = OR_RETURN(measureFsVerity(fd));
198             digests[it->path()] = digest;
199         }
200     }
201     if (ec) {
202         return Error() << "Failed to iterate " << path << ": " << ec.message();
203     }
204 
205     return digests;
206 }
207 
verifyAllFilesInVerity(const std::string & path)208 Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) {
209     std::map<std::string, std::string> digests;
210     std::error_code ec;
211 
212     auto it = std::filesystem::recursive_directory_iterator(path, ec);
213     auto end = std::filesystem::recursive_directory_iterator();
214 
215     while (!ec && it != end) {
216         if (it->is_regular_file()) {
217             // Verify the file is in fs-verity
218             auto result = OR_RETURN(measureFsVerity(it->path()));
219             digests[it->path()] = result;
220         } else if (it->is_directory()) {
221             // These are fine to ignore
222         } else if (it->is_symlink()) {
223             return Error() << "Rejecting artifacts, symlink at " << it->path();
224         } else {
225             return Error() << "Rejecting artifacts, unexpected file type for " << it->path();
226         }
227         ++it;
228     }
229     if (ec) {
230         return Error() << "Failed to iterate " << path << ": " << ec;
231     }
232 
233     return digests;
234 }
235 
verifyAllFilesUsingCompOs(const std::string & directory_path,const std::map<std::string,std::string> & digests)236 Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path,
237                                        const std::map<std::string, std::string>& digests) {
238     std::error_code ec;
239     size_t verified_count = 0;
240     auto it = std::filesystem::recursive_directory_iterator(directory_path, ec);
241     for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
242         auto& path = it->path();
243         if (it->is_regular_file()) {
244             auto entry = digests.find(path);
245             if (entry == digests.end()) {
246                 return Error() << "Unexpected file found: " << path;
247             }
248             auto& compos_digest = entry->second;
249 
250             unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
251             if (!fd.ok()) {
252                 return ErrnoError() << "Can't open " << path;
253             }
254 
255             bool enabled = OR_RETURN(isFileInVerity(fd));
256             if (!enabled) {
257                 LOG(INFO) << "Enabling fs-verity for " << path;
258                 OR_RETURN(enableFsVerity(fd));
259             }
260 
261             auto actual_digest = OR_RETURN(measureFsVerity(fd));
262             // Make sure the file's fs-verity digest matches the known value.
263             if (actual_digest == compos_digest) {
264                 ++verified_count;
265             } else {
266                 return Error() << "fs-verity digest does not match CompOS digest: " << path;
267             }
268         } else if (it->is_directory()) {
269             // These are fine to ignore
270         } else if (it->is_symlink()) {
271             return Error() << "Rejecting artifacts, symlink at " << path;
272         } else {
273             return Error() << "Rejecting artifacts, unexpected file type for " << path;
274         }
275     }
276     if (ec) {
277         return Error() << "Failed to iterate " << directory_path << ": " << ec.message();
278     }
279 
280     // Make sure all the files we expected have been seen
281     if (verified_count != digests.size()) {
282         return Error() << "Verified " << verified_count << " files, but expected "
283                        << digests.size();
284     }
285 
286     return {};
287 }
288 
addCertToFsVerityKeyring(const std::string & path,const char * keyName)289 Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) {
290     const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName};
291 
292     int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
293     if (fd == -1) {
294         return ErrnoError() << "Failed to open " << path;
295     }
296     pid_t pid = fork();
297     if (pid == 0) {
298         dup2(fd, STDIN_FILENO);
299         close(fd);
300         int argc = arraysize(argv);
301         char* argv_child[argc + 1];
302         memcpy(argv_child, argv, argc * sizeof(char*));
303         argv_child[argc] = nullptr;
304         execvp(argv_child[0], argv_child);
305         PLOG(ERROR) << "exec in ForkExecvp";
306         _exit(EXIT_FAILURE);
307     } else {
308         close(fd);
309     }
310     if (pid == -1) {
311         return ErrnoError() << "Failed to fork.";
312     }
313     int status;
314     if (waitpid(pid, &status, 0) == -1) {
315         return ErrnoError() << "waitpid() failed.";
316     }
317     if (!WIFEXITED(status)) {
318         return Error() << kFsVerityInitPath << ": abnormal process exit";
319     }
320     if (WEXITSTATUS(status) != 0) {
321         return Error() << kFsVerityInitPath << " exited with " << WEXITSTATUS(status);
322     }
323 
324     return {};
325 }
326