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, ¶ms, &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