1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <dirent.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <inttypes.h>
9 #include <stddef.h>
10 #include <stdint.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <threads.h>
15 #include <unistd.h>
16
17 #include <fbl/algorithm.h>
18 #include <fbl/auto_call.h>
19 #include <fbl/auto_lock.h>
20 #include <fbl/string.h>
21 #include <fbl/unique_fd.h>
22 #include <fs-management/fvm.h>
23 #include <fs-management/mount.h>
24 #include <fs-management/ramdisk.h>
25 #include <fvm/fvm.h>
26 #include <lib/fdio/debug.h>
27 #include <lib/fdio/watcher.h>
28 #include <lib/zx/time.h>
29 #include <unittest/unittest.h>
30 #include <zircon/assert.h>
31 #include <zircon/types.h>
32 #include <zxcrypt/volume.h>
33
34 #include "test-device.h"
35
36 #define ZXDEBUG 0
37
38 namespace zxcrypt {
39 namespace testing {
40 namespace {
41
42 // No test step should take longer than this
43 const zx::duration kTimeout = zx::sec(3);
44
45 // FVM driver library
46 const char* kFvmDriver = "/boot/driver/fvm.so";
47
48 // Takes a given |result|, e.g. from an ioctl, and translates into a zx_status_t.
ToStatus(ssize_t result)49 zx_status_t ToStatus(ssize_t result) {
50 return result < 0 ? static_cast<zx_status_t>(result) : ZX_OK;
51 }
52
53 // Helper function to build error messages
Error(const char * fmt,...)54 char* Error(const char* fmt, ...) {
55 static char err[256];
56 va_list ap;
57 va_start(ap, fmt);
58 vsnprintf(err, sizeof(err), fmt, ap);
59 va_end(ap);
60 return err;
61 }
62
63 // Waits for the given |path| to be opened, opens it, and returns the file descriptor via |out|.
WaitAndOpen(char * path,fbl::unique_fd * out)64 bool WaitAndOpen(char* path, fbl::unique_fd* out) {
65 BEGIN_HELPER;
66
67 ASSERT_EQ(wait_for_device(path, ZX_SEC(3)), ZX_OK,
68 Error("failed while waiting to bind %s", path));
69 fbl::unique_fd fd(open(path, O_RDWR));
70 ASSERT_TRUE(fd, Error("failed to open %s", path));
71 if (out) {
72 out->swap(fd);
73 }
74
75 END_HELPER;
76 }
77
78 } // namespace
79
TestDevice()80 TestDevice::TestDevice()
81 : block_count_(0), block_size_(0), client_(nullptr), tid_(0), need_join_(false), wake_after_(0),
82 wake_deadline_(0) {
83 memset(ramdisk_path_, 0, sizeof(ramdisk_path_));
84 memset(fvm_part_path_, 0, sizeof(fvm_part_path_));
85 memset(&req_, 0, sizeof(req_));
86 }
87
~TestDevice()88 TestDevice::~TestDevice() {
89 Disconnect();
90 ramdisk_.reset();
91 DestroyRamdisk();
92 if (need_join_) {
93 int res;
94 thrd_join(tid_, &res);
95 }
96 }
97
Create(size_t device_size,size_t block_size,bool fvm)98 bool TestDevice::Create(size_t device_size, size_t block_size, bool fvm) {
99 BEGIN_HELPER;
100
101 ASSERT_LT(device_size, SSIZE_MAX);
102 if (fvm) {
103 ASSERT_TRUE(CreateFvmPart(device_size, block_size));
104 } else {
105 ASSERT_TRUE(CreateRamdisk(device_size, block_size));
106 }
107
108 // TODO(aarongreen): See ZX-1130. The code below should be enabled when that bug is fixed.
109 #if 0
110 crypto::digest::Algorithm digest;
111 switch (version) {
112 case Volume::kAES256_XTS_SHA256:
113 digest = crypto::digest::kSHA256;
114 break;
115 default:
116 digest = crypto::digest::kUninitialized;
117 break;
118 }
119
120 size_t digest_len;
121 key_.Reset();
122 if ((rc = crypto::digest::GetDigestLen(digest, &digest_len)) != ZX_OK ||
123 (rc = key_.Randomize(digest_len)) != ZX_OK) {
124 return rc;
125 }
126 #else
127 uint8_t* buf;
128 ASSERT_OK(key_.Allocate(kZx1130KeyLen, &buf));
129 memset(buf, 0, key_.len());
130 #endif
131
132 END_HELPER;
133 }
134
Bind(Volume::Version version,bool fvm)135 bool TestDevice::Bind(Volume::Version version, bool fvm) {
136 BEGIN_HELPER;
137 ASSERT_TRUE(Create(kDeviceSize, kBlockSize, fvm));
138 ASSERT_OK(Volume::Create(parent(), key_));
139 ASSERT_TRUE(Connect());
140 END_HELPER;
141 }
142
Rebind()143 bool TestDevice::Rebind() {
144 BEGIN_HELPER;
145
146 const char* sep = strrchr(ramdisk_path_, '/');
147 ASSERT_NONNULL(sep);
148 fbl::String path(ramdisk_path_, sep - ramdisk_path_);
149 DIR* dir = opendir(path.c_str());
150 ASSERT_NONNULL(dir);
151 auto close_dir = fbl::MakeAutoCall([&] { closedir(dir); });
152
153 zx::time deadline = zx::deadline_after(kTimeout);
154
155 Disconnect();
156 zxcrypt_.reset();
157 fvm_part_.reset();
158 ASSERT_EQ(fdio_watch_directory(dirfd(dir), RebindWatcher, deadline.get(), this), ZX_ERR_STOP);
159 ASSERT_TRUE(WaitAndOpen(ramdisk_path_, &ramdisk_));
160 if (strlen(fvm_part_path_) != 0) {
161 ASSERT_TRUE(WaitAndOpen(fvm_part_path_, &fvm_part_));
162 }
163 ASSERT_TRUE(Connect());
164
165 END_HELPER;
166 }
167
RebindWatcher(int dirfd,int event,const char * fn,void * cookie)168 zx_status_t TestDevice::RebindWatcher(int dirfd, int event, const char* fn, void* cookie) {
169 TestDevice* device = static_cast<TestDevice*>(cookie);
170 switch (event) {
171 case WATCH_EVENT_IDLE:
172 return ToStatus(ioctl_block_rr_part(device->ramdisk_.get()));
173 case WATCH_EVENT_REMOVE_FILE:
174 return strcmp(fn, "block") == 0 ? ZX_ERR_STOP : ZX_OK;
175 default:
176 return ZX_OK;
177 }
178 }
179
SleepUntil(uint64_t num,bool deferred)180 bool TestDevice::SleepUntil(uint64_t num, bool deferred) {
181 BEGIN_HELPER;
182 fbl::AutoLock lock(&lock_);
183 ASSERT_EQ(wake_after_, 0);
184 ASSERT_NE(num, 0);
185 wake_after_ = num;
186 wake_deadline_ = zx::deadline_after(kTimeout);
187 ASSERT_EQ(thrd_create(&tid_, TestDevice::WakeThread, this), thrd_success);
188 need_join_ = true;
189 if (deferred) {
190 uint32_t flags = RAMDISK_FLAG_RESUME_ON_WAKE;
191 ASSERT_OK(ToStatus(ioctl_ramdisk_set_flags(ramdisk_.get(), &flags)));
192 }
193 uint64_t sleep_after = 0;
194 ASSERT_OK(ToStatus(ioctl_ramdisk_sleep_after(ramdisk_.get(), &sleep_after)));
195 END_HELPER;
196 }
197
WakeUp()198 bool TestDevice::WakeUp() {
199 BEGIN_HELPER;
200 if (need_join_) {
201 fbl::AutoLock lock(&lock_);
202 ASSERT_NE(wake_after_, 0);
203 int res;
204 ASSERT_EQ(thrd_join(tid_, &res), thrd_success);
205 need_join_ = false;
206 wake_after_ = 0;
207 EXPECT_EQ(res, 0);
208 }
209 END_HELPER;
210 }
211
WakeThread(void * arg)212 int TestDevice::WakeThread(void* arg) {
213 TestDevice* device = static_cast<TestDevice*>(arg);
214 fbl::AutoLock lock(&device->lock_);
215
216 // Always send a wake-up call; even if we failed to go to sleep.
217 auto cleanup = fbl::MakeAutoCall([&] { ioctl_ramdisk_wake_up(device->ramdisk_.get()); });
218
219 // Loop until timeout, |wake_after_| txns received, or error getting counts
220 ramdisk_blk_counts_t counts;
221 ssize_t res;
222 do {
223 zx::nanosleep(zx::deadline_after(zx::msec(100)));
224 if (device->wake_deadline_ < zx::clock::get_monotonic()) {
225 printf("Received %lu of %lu transactions before timing out.\n", counts.received,
226 device->wake_after_);
227 return ZX_ERR_TIMED_OUT;
228 }
229 if ((res = ioctl_ramdisk_get_blk_counts(device->ramdisk_.get(), &counts)) < 0) {
230 return static_cast<zx_status_t>(res);
231 }
232 } while (counts.received < device->wake_after_);
233 return ZX_OK;
234 }
235
ReadFd(zx_off_t off,size_t len)236 bool TestDevice::ReadFd(zx_off_t off, size_t len) {
237 BEGIN_HELPER;
238 ASSERT_OK(ToStatus(lseek(off)));
239 ASSERT_OK(ToStatus(read(off, len)));
240 ASSERT_EQ(memcmp(as_read_.get() + off, to_write_.get() + off, len), 0);
241 END_HELPER;
242 }
243
WriteFd(zx_off_t off,size_t len)244 bool TestDevice::WriteFd(zx_off_t off, size_t len) {
245 BEGIN_HELPER;
246 ASSERT_OK(ToStatus(lseek(off)));
247 ASSERT_OK(ToStatus(write(off, len)));
248 END_HELPER;
249 }
250
ReadVmo(zx_off_t off,size_t len)251 bool TestDevice::ReadVmo(zx_off_t off, size_t len) {
252 BEGIN_HELPER;
253 ASSERT_OK(block_fifo_txn(BLOCKIO_READ, off, len));
254 off *= block_size_;
255 len *= block_size_;
256 ASSERT_OK(vmo_read(off, len));
257 ASSERT_EQ(memcmp(as_read_.get() + off, to_write_.get() + off, len), 0);
258 END_HELPER;
259 }
260
WriteVmo(zx_off_t off,size_t len)261 bool TestDevice::WriteVmo(zx_off_t off, size_t len) {
262 BEGIN_HELPER;
263 ASSERT_OK(vmo_write(off * block_size_, len * block_size_));
264 ASSERT_OK(block_fifo_txn(BLOCKIO_WRITE, off, len));
265 END_HELPER;
266 }
267
Corrupt(uint64_t blkno,key_slot_t slot)268 bool TestDevice::Corrupt(uint64_t blkno, key_slot_t slot) {
269 BEGIN_HELPER;
270 uint8_t block[block_size_];
271
272 fbl::unique_fd fd = parent();
273 ASSERT_OK(ToStatus(::lseek(fd.get(), blkno * block_size_, SEEK_SET)));
274 ASSERT_OK(ToStatus(::read(fd.get(), block, block_size_)));
275
276 fbl::unique_ptr<Volume> volume;
277 ASSERT_OK(Volume::Unlock(parent(), key_, 0, &volume));
278
279 zx_off_t off;
280 ASSERT_OK(volume->GetSlotOffset(slot, &off));
281 int flip = 1U << (rand() % 8);
282 block[off] ^= static_cast<uint8_t>(flip);
283
284 ASSERT_OK(ToStatus(::lseek(fd.get(), blkno * block_size_, SEEK_SET)));
285 ASSERT_OK(ToStatus(::write(fd.get(), block, block_size_)));
286 END_HELPER;
287 }
288
289 // Private methods
290
CreateRamdisk(size_t device_size,size_t block_size)291 bool TestDevice::CreateRamdisk(size_t device_size, size_t block_size) {
292 BEGIN_HELPER;
293
294 fbl::AllocChecker ac;
295 size_t count = fbl::round_up(device_size, block_size) / block_size;
296 to_write_.reset(new (&ac) uint8_t[device_size]);
297 ASSERT_TRUE(ac.check());
298 for (size_t i = 0; i < device_size; ++i) {
299 to_write_[i] = static_cast<uint8_t>(rand());
300 }
301
302 as_read_.reset(new (&ac) uint8_t[device_size]);
303 ASSERT_TRUE(ac.check());
304 memset(as_read_.get(), 0, block_size);
305
306 ASSERT_EQ(create_ramdisk(block_size, count, ramdisk_path_), ZX_OK);
307 ramdisk_.reset(open(ramdisk_path_, O_RDWR));
308 ASSERT_TRUE(ramdisk_, Error("failed to open %s", ramdisk_path_));
309
310 block_size_ = block_size;
311 block_count_ = count;
312
313 END_HELPER;
314 }
315
DestroyRamdisk()316 void TestDevice::DestroyRamdisk() {
317 if (strlen(ramdisk_path_) != 0) {
318 destroy_ramdisk(ramdisk_path_);
319 ramdisk_path_[0] = '\0';
320 }
321 }
322
323 // Creates a ramdisk, formats it, and binds to it.
CreateFvmPart(size_t device_size,size_t block_size)324 bool TestDevice::CreateFvmPart(size_t device_size, size_t block_size) {
325 BEGIN_HELPER;
326
327 // Calculate total size of data + metadata.
328 device_size = fbl::round_up(device_size, FVM_BLOCK_SIZE);
329 size_t old_meta = fvm::MetadataSize(device_size, FVM_BLOCK_SIZE);
330 size_t new_meta = fvm::MetadataSize(old_meta + device_size, FVM_BLOCK_SIZE);
331 while (old_meta != new_meta) {
332 old_meta = new_meta;
333 new_meta = fvm::MetadataSize(old_meta + device_size, FVM_BLOCK_SIZE);
334 }
335 ASSERT_TRUE(CreateRamdisk(device_size + (new_meta * 2), block_size));
336
337 // Format the ramdisk as FVM and bind to it
338 ASSERT_OK(fvm_init(ramdisk_.get(), FVM_BLOCK_SIZE));
339 ASSERT_OK(ToStatus(ioctl_device_bind(ramdisk_.get(), kFvmDriver, strlen(kFvmDriver))));
340
341 char path[PATH_MAX];
342 fbl::unique_fd fvm_fd;
343 snprintf(path, sizeof(path), "%s/fvm", ramdisk_path_);
344 ASSERT_TRUE(WaitAndOpen(path, &fvm_fd));
345
346 // Allocate a FVM partition with the last slice unallocated.
347 alloc_req_t req;
348 memset(&req, 0, sizeof(alloc_req_t));
349 req.slice_count = (kDeviceSize / FVM_BLOCK_SIZE) - 1;
350 memcpy(req.type, zxcrypt_magic, sizeof(zxcrypt_magic));
351 for (uint8_t i = 0; i < GUID_LEN; ++i) {
352 req.guid[i] = i;
353 }
354 snprintf(req.name, NAME_LEN, "data");
355 fvm_part_.reset(fvm_allocate_partition(fvm_fd.get(), &req));
356 ASSERT_TRUE(fvm_part_);
357
358 // Save the topological path for rebinding
359 ASSERT_OK(ToStatus(
360 ioctl_device_get_topo_path(fvm_part_.get(), fvm_part_path_, sizeof(fvm_part_path_))));
361
362 END_HELPER;
363 }
364
Connect()365 bool TestDevice::Connect() {
366 BEGIN_HELPER;
367 ZX_DEBUG_ASSERT(!zxcrypt_);
368
369 ASSERT_OK(Volume::Unlock(parent(), key_, 0, &volume_));
370 ASSERT_OK(volume_->Open(kTimeout, &zxcrypt_));
371
372 block_info_t blk;
373 ASSERT_OK(ToStatus(ioctl_block_get_info(zxcrypt_.get(), &blk)));
374 block_size_ = blk.block_size;
375 block_count_ = blk.block_count;
376
377 zx_handle_t fifo;
378 ASSERT_OK(ToStatus(ioctl_block_get_fifos(zxcrypt_.get(), &fifo)));
379 req_.group = 0;
380 ASSERT_OK(block_fifo_create_client(fifo, &client_));
381
382 // Create the vmo and get a transferable handle to give to the block server
383 ASSERT_OK(zx::vmo::create(size(), 0, &vmo_));
384 zx_handle_t xfer;
385 ASSERT_OK(zx_handle_duplicate(vmo_.get(), ZX_RIGHT_SAME_RIGHTS, &xfer));
386 ASSERT_OK(ToStatus(ioctl_block_attach_vmo(zxcrypt_.get(), &xfer, &req_.vmoid)));
387
388 END_HELPER;
389 }
390
Disconnect()391 void TestDevice::Disconnect() {
392 if (client_) {
393 memset(&req_, 0, sizeof(req_));
394 block_fifo_release_client(client_);
395 client_ = nullptr;
396 }
397 zxcrypt_.reset();
398 volume_.reset();
399 block_size_ = 0;
400 block_count_ = 0;
401 vmo_.reset();
402 }
403
404 } // namespace testing
405 } // namespace zxcrypt
406