• 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/file.h>
30 #include <android-base/logging.h>
31 #include <android-base/unique_fd.h>
32 #include <asm/byteorder.h>
33 #include <libfsverity.h>
34 #include <linux/fsverity.h>
35 
36 #include "CertUtils.h"
37 #include "SigningKey.h"
38 
39 #define FS_VERITY_MAX_DIGEST_SIZE 64
40 
41 using android::base::ErrnoError;
42 using android::base::Error;
43 using android::base::Result;
44 using android::base::unique_fd;
45 
46 static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
47 static const char* kFsVerityProcPath = "/proc/sys/fs/verity";
48 
SupportsFsVerity()49 bool SupportsFsVerity() {
50     return access(kFsVerityProcPath, F_OK) == 0;
51 }
52 
toHex(std::span<const uint8_t> data)53 static std::string toHex(std::span<const uint8_t> data) {
54     std::stringstream ss;
55     for (auto it = data.begin(); it != data.end(); ++it) {
56         ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
57     }
58     return ss.str();
59 }
60 
fromHex(std::string_view hex)61 static std::vector<uint8_t> fromHex(std::string_view hex) {
62     if (hex.size() % 2 != 0) {
63         return {};
64     }
65     std::vector<uint8_t> result;
66     result.reserve(hex.size() / 2);
67     for (size_t i = 0; i < hex.size(); i += 2) {
68         uint8_t byte;
69         auto conversion_result = std::from_chars(&hex[i], &hex[i + 2], byte, 16);
70         if (conversion_result.ptr != &hex[i + 2] || conversion_result.ec != std::errc()) {
71             return {};
72         }
73         result.push_back(byte);
74     }
75     return result;
76 }
77 
read_callback(void * file,void * buf,size_t count)78 static int read_callback(void* file, void* buf, size_t count) {
79     int* fd = (int*)file;
80     if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO;
81     return 0;
82 }
83 
createDigest(int fd)84 static Result<std::vector<uint8_t>> createDigest(int fd) {
85     struct stat filestat;
86     int ret = fstat(fd, &filestat);
87     if (ret < 0) {
88         return ErrnoError() << "Failed to fstat";
89     }
90     struct libfsverity_merkle_tree_params params = {
91         .version = 1,
92         .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
93         .file_size = static_cast<uint64_t>(filestat.st_size),
94         .block_size = 4096,
95     };
96 
97     struct libfsverity_digest* digest;
98     ret = libfsverity_compute_digest(&fd, &read_callback, &params, &digest);
99     if (ret < 0) {
100         return ErrnoError() << "Failed to compute fs-verity digest";
101     }
102     int expected_digest_size = libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256);
103     if (digest->digest_size != expected_digest_size) {
104         return Error() << "Digest does not have expected size: " << expected_digest_size
105                        << " actual: " << digest->digest_size;
106     }
107     std::vector<uint8_t> digestVector(&digest->digest[0], &digest->digest[expected_digest_size]);
108     free(digest);
109     return digestVector;
110 }
111 
createDigest(const std::string & path)112 Result<std::vector<uint8_t>> createDigest(const std::string& path) {
113     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
114     if (!fd.ok()) {
115         return ErrnoError() << "Unable to open";
116     }
117     return createDigest(fd.get());
118 }
119 
120 namespace {
121 template <typename T> struct DeleteAsPODArray {
operator ()__anon82a2ba030111::DeleteAsPODArray122     void operator()(T* x) {
123         if (x) {
124             x->~T();
125             delete[](uint8_t*) x;
126         }
127     }
128 };
129 
measureFsVerity(int fd,const fsverity_digest * digest)130 static Result<void> measureFsVerity(int fd, const fsverity_digest* digest) {
131     if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) != 0) {
132         if (errno == ENODATA) {
133             return Error() << "File is not in fs-verity";
134         } else {
135             return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
136         }
137     }
138 
139     return {};
140 }
141 
142 }  // namespace
143 
144 template <typename T> using trailing_unique_ptr = std::unique_ptr<T, DeleteAsPODArray<T>>;
145 
146 template <typename T>
makeUniqueWithTrailingData(size_t trailing_data_size)147 static trailing_unique_ptr<T> makeUniqueWithTrailingData(size_t trailing_data_size) {
148     uint8_t* memory = new uint8_t[sizeof(T) + trailing_data_size];
149     T* ptr = new (memory) T;
150     return trailing_unique_ptr<T>{ptr};
151 }
152 
signDigest(const SigningKey & key,const std::vector<uint8_t> & digest)153 static Result<std::vector<uint8_t>> signDigest(const SigningKey& key,
154                                                const std::vector<uint8_t>& digest) {
155     auto d = makeUniqueWithTrailingData<fsverity_formatted_digest>(digest.size());
156 
157     memcpy(d->magic, "FSVerity", 8);
158     d->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
159     d->digest_size = __cpu_to_le16(digest.size());
160     memcpy(d->digest, digest.data(), digest.size());
161 
162     auto signed_digest = key.sign(std::string((char*)d.get(), sizeof(*d) + digest.size()));
163     if (!signed_digest.ok()) {
164         return signed_digest.error();
165     }
166 
167     return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end());
168 }
169 
enableFsVerity(int fd,std::span<uint8_t> pkcs7)170 static Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) {
171     struct fsverity_enable_arg arg = {.version = 1};
172 
173     arg.sig_ptr = reinterpret_cast<uint64_t>(pkcs7.data());
174     arg.sig_size = pkcs7.size();
175     arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
176     arg.block_size = 4096;
177 
178     int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
179 
180     if (ret != 0) {
181         return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY";
182     }
183 
184     return {};
185 }
186 
enableFsVerity(int fd,const SigningKey & key)187 Result<std::string> enableFsVerity(int fd, const SigningKey& key) {
188     auto digest = createDigest(fd);
189     if (!digest.ok()) {
190         return Error() << digest.error();
191     }
192 
193     auto signed_digest = signDigest(key, digest.value());
194     if (!signed_digest.ok()) {
195         return signed_digest.error();
196     }
197 
198     auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject);
199     if (!pkcs7_data.ok()) {
200         return pkcs7_data.error();
201     }
202 
203     auto enabled = enableFsVerity(fd, pkcs7_data.value());
204     if (!enabled.ok()) {
205         return Error() << enabled.error();
206     }
207 
208     // Return the root hash as a hex string
209     return toHex(digest.value());
210 }
211 
isFileInVerity(int fd)212 static Result<std::string> isFileInVerity(int fd) {
213     auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
214     d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
215 
216     const auto& status = measureFsVerity(fd, d.get());
217     if (!status.ok()) {
218         return status.error();
219     }
220 
221     return toHex({&d->digest[0], &d->digest[d->digest_size]});
222 }
223 
isFileInVerity(const std::string & path)224 static Result<std::string> isFileInVerity(const std::string& path) {
225     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
226     if (!fd.ok()) {
227         return ErrnoError() << "Failed to open " << path;
228     }
229 
230     auto digest = isFileInVerity(fd.get());
231     if (!digest.ok()) {
232         return Error() << digest.error() << ": " << path;
233     }
234 
235     return digest;
236 }
237 
addFilesToVerityRecursive(const std::string & path,const SigningKey & key)238 Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path,
239                                                                      const SigningKey& key) {
240     std::map<std::string, std::string> digests;
241 
242     std::error_code ec;
243     auto it = std::filesystem::recursive_directory_iterator(path, ec);
244     for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
245         if (it->is_regular_file()) {
246             unique_fd fd(TEMP_FAILURE_RETRY(open(it->path().c_str(), O_RDONLY | O_CLOEXEC)));
247             if (!fd.ok()) {
248                 return ErrnoError() << "Failed to open " << path;
249             }
250             auto digest = isFileInVerity(fd);
251             if (!digest.ok()) {
252                 LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
253                 auto result = enableFsVerity(fd, key);
254                 if (!result.ok()) {
255                     return result.error();
256                 }
257                 digests[it->path()] = *result;
258             } else {
259                 LOG(INFO) << it->path() << " was already in fs-verity.";
260                 digests[it->path()] = *digest;
261             }
262         }
263     }
264     if (ec) {
265         return Error() << "Failed to iterate " << path << ": " << ec.message();
266     }
267 
268     return digests;
269 }
270 
enableFsVerity(const std::string & path,const std::string & signature_path)271 Result<void> enableFsVerity(const std::string& path, const std::string& signature_path) {
272     unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
273     if (!fd.ok()) {
274         return Error() << "Can't open " << path;
275     }
276 
277     std::string signature;
278     android::base::ReadFileToString(signature_path, &signature);
279     std::vector<uint8_t> span = std::vector<uint8_t>(signature.begin(), signature.end());
280 
281     const auto& enable = enableFsVerity(fd.get(), span);
282     if (!enable.ok()) {
283         return enable.error();
284     }
285 
286     auto digest = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
287     digest->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
288     const auto& measure = measureFsVerity(fd.get(), digest.get());
289     if (!measure.ok()) {
290         return measure.error();
291     }
292 
293     return {};
294 }
295 
verifyAllFilesInVerity(const std::string & path)296 Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) {
297     std::map<std::string, std::string> digests;
298     std::error_code ec;
299 
300     auto it = std::filesystem::recursive_directory_iterator(path, ec);
301     auto end = std::filesystem::recursive_directory_iterator();
302 
303     while (!ec && it != end) {
304         if (it->is_regular_file()) {
305             // Verify the file is in fs-verity
306             auto result = isFileInVerity(it->path());
307             if (!result.ok()) {
308                 return result.error();
309             }
310             digests[it->path()] = *result;
311         } else if (it->is_directory()) {
312             // These are fine to ignore
313         } else if (it->is_symlink()) {
314             return Error() << "Rejecting artifacts, symlink at " << it->path();
315         } else {
316             return Error() << "Rejecting artifacts, unexpected file type for " << it->path();
317         }
318         ++it;
319     }
320     if (ec) {
321         return Error() << "Failed to iterate " << path << ": " << ec;
322     }
323 
324     return digests;
325 }
326 
verifyAllFilesUsingCompOs(const std::string & directory_path,const std::map<std::string,std::string> & digests,const SigningKey & signing_key)327 Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path,
328                                        const std::map<std::string, std::string>& digests,
329                                        const SigningKey& signing_key) {
330     std::error_code ec;
331     size_t verified_count = 0;
332     auto it = std::filesystem::recursive_directory_iterator(directory_path, ec);
333     for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
334         auto& path = it->path();
335         if (it->is_regular_file()) {
336             auto entry = digests.find(path);
337             if (entry == digests.end()) {
338                 return Error() << "Unexpected file found: " << path;
339             }
340             auto& compos_digest = entry->second;
341 
342             unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
343             if (!fd.ok()) {
344                 return ErrnoError() << "Can't open " << path;
345             }
346 
347             auto verity_digest = isFileInVerity(fd);
348             if (verity_digest.ok()) {
349                 // The file is already in fs-verity. We need to make sure it was signed
350                 // by CompOS, so we just check that it has the digest we expect.
351                 if (verity_digest.value() == compos_digest) {
352                     ++verified_count;
353                 } else {
354                     return Error() << "fs-verity digest does not match CompOS digest: " << path;
355                 }
356             } else {
357                 // Not in fs-verity yet. We know the digest CompOS provided; If
358                 // it's not the correct digest for the file then enabling
359                 // fs-verity will fail, so we don't need to check it explicitly
360                 // ourselves. Otherwise we should be good.
361                 LOG(INFO) << "Adding " << path << " to fs-verity...";
362 
363                 auto digest_bytes = fromHex(compos_digest);
364                 if (digest_bytes.empty()) {
365                     return Error() << "Invalid digest " << compos_digest;
366                 }
367                 auto signed_digest = signDigest(signing_key, digest_bytes);
368                 if (!signed_digest.ok()) {
369                     return signed_digest.error();
370                 }
371 
372                 auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject);
373                 if (!pkcs7_data.ok()) {
374                     return pkcs7_data.error();
375                 }
376 
377                 auto enabled = enableFsVerity(fd, pkcs7_data.value());
378                 if (!enabled.ok()) {
379                     return Error() << enabled.error();
380                 }
381                 ++verified_count;
382             }
383         } else if (it->is_directory()) {
384             // These are fine to ignore
385         } else if (it->is_symlink()) {
386             return Error() << "Rejecting artifacts, symlink at " << path;
387         } else {
388             return Error() << "Rejecting artifacts, unexpected file type for " << path;
389         }
390     }
391     if (ec) {
392         return Error() << "Failed to iterate " << directory_path << ": " << ec.message();
393     }
394 
395     // Make sure all the files we expected have been seen
396     if (verified_count != digests.size()) {
397         return Error() << "Verified " << verified_count << " files, but expected "
398                        << digests.size();
399     }
400 
401     return {};
402 }
403 
addCertToFsVerityKeyring(const std::string & path,const char * keyName)404 Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) {
405     const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName};
406 
407     int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
408     if (fd == -1) {
409         return ErrnoError() << "Failed to open " << path;
410     }
411     pid_t pid = fork();
412     if (pid == 0) {
413         dup2(fd, STDIN_FILENO);
414         close(fd);
415         int argc = arraysize(argv);
416         char* argv_child[argc + 1];
417         memcpy(argv_child, argv, argc * sizeof(char*));
418         argv_child[argc] = nullptr;
419         execvp(argv_child[0], argv_child);
420         PLOG(ERROR) << "exec in ForkExecvp";
421         _exit(EXIT_FAILURE);
422     } else {
423         close(fd);
424     }
425     if (pid == -1) {
426         return ErrnoError() << "Failed to fork.";
427     }
428     int status;
429     if (waitpid(pid, &status, 0) == -1) {
430         return ErrnoError() << "waitpid() failed.";
431     }
432     if (!WIFEXITED(status)) {
433         return Error() << kFsVerityInitPath << ": abnormal process exit";
434     }
435     if (WEXITSTATUS(status) != 0) {
436         return Error() << kFsVerityInitPath << " exited with " << WEXITSTATUS(status);
437     }
438 
439     return {};
440 }
441