aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/md/dm-verity-avb.c')
-rw-r--r--drivers/md/dm-verity-avb.c217
1 files changed, 217 insertions, 0 deletions
diff --git a/drivers/md/dm-verity-avb.c b/drivers/md/dm-verity-avb.c
new file mode 100644
index 000000000000..88487346c4c6
--- /dev/null
+++ b/drivers/md/dm-verity-avb.c
@@ -0,0 +1,217 @@
1/*
2 * Copyright (C) 2017 Google.
3 *
4 * This file is released under the GPLv2.
5 *
6 * Based on drivers/md/dm-verity-chromeos.c
7 */
8
9#include <linux/device-mapper.h>
10#include <linux/module.h>
11#include <linux/mount.h>
12
13#define DM_MSG_PREFIX "verity-avb"
14
15/* Set via module parameter. */
16static char avb_vbmeta_device[64];
17
18static void invalidate_vbmeta_endio(struct bio *bio)
19{
20 complete(bio->bi_private);
21}
22
23static int invalidate_vbmeta_submit(struct bio *bio,
24 struct block_device *bdev,
25 int rw, int access_last_sector,
26 struct page *page)
27{
28 DECLARE_COMPLETION_ONSTACK(wait);
29
30 bio->bi_private = &wait;
31 bio->bi_end_io = invalidate_vbmeta_endio;
32 bio->bi_bdev = bdev;
33
34 bio->bi_iter.bi_sector = 0;
35 if (access_last_sector) {
36 sector_t last_sector = (i_size_read(bdev->bd_inode)>>SECTOR_SHIFT) - 1;
37 bio->bi_iter.bi_sector = last_sector;
38 }
39 bio->bi_vcnt = 1;
40 bio->bi_iter.bi_idx = 0;
41 bio->bi_iter.bi_size = 512;
42 bio->bi_iter.bi_bvec_done = 0;
43 bio->bi_rw = rw;
44 bio->bi_io_vec[0].bv_page = page;
45 bio->bi_io_vec[0].bv_len = 512;
46 bio->bi_io_vec[0].bv_offset = 0;
47
48 submit_bio(rw, bio);
49 /* Wait up to 2 seconds for completion or fail. */
50 if (!wait_for_completion_timeout(&wait, msecs_to_jiffies(2000)))
51 return -EIO;
52 return 0;
53}
54
55static int invalidate_vbmeta(dev_t vbmeta_devt)
56{
57 int ret = 0;
58 struct block_device *bdev;
59 struct bio *bio;
60 struct page *page;
61 fmode_t dev_mode;
62 /* Ensure we do synchronous unblocked I/O. We may also need
63 * sync_bdev() on completion, but it really shouldn't.
64 */
65 int rw = REQ_SYNC | REQ_SOFTBARRIER | REQ_NOIDLE;
66 int access_last_sector = 0;
67
68 /* First we open the device for reading. */
69 dev_mode = FMODE_READ | FMODE_EXCL;
70 bdev = blkdev_get_by_dev(vbmeta_devt, dev_mode,
71 invalidate_vbmeta);
72 if (IS_ERR(bdev)) {
73 DMERR("invalidate_kernel: could not open device for reading");
74 dev_mode = 0;
75 ret = -ENOENT;
76 goto failed_to_read;
77 }
78
79 bio = bio_alloc(GFP_NOIO, 1);
80 if (!bio) {
81 ret = -ENOMEM;
82 goto failed_bio_alloc;
83 }
84
85 page = alloc_page(GFP_NOIO);
86 if (!page) {
87 ret = -ENOMEM;
88 goto failed_to_alloc_page;
89 }
90
91 access_last_sector = 0;
92 ret = invalidate_vbmeta_submit(bio, bdev, rw, access_last_sector, page);
93 if (ret) {
94 DMERR("invalidate_vbmeta: error reading");
95 goto failed_to_submit_read;
96 }
97
98 /* We have a page. Let's make sure it looks right. */
99 if (memcmp("AVB0", page_address(page), 4) == 0) {
100 /* Stamp it. */
101 memcpy(page_address(page), "AVE0", 4);
102 DMINFO("invalidate_vbmeta: found vbmeta partition");
103 } else {
104 /* Could be this is on a AVB footer, check. Also, since the
105 * AVB footer is in the last 64 bytes, adjust for the fact that
106 * we're dealing with 512-byte sectors.
107 */
108 size_t offset = (1<<SECTOR_SHIFT) - 64;
109
110 access_last_sector = 1;
111 ret = invalidate_vbmeta_submit(bio, bdev, rw,
112 access_last_sector, page);
113 if (ret) {
114 DMERR("invalidate_vbmeta: error reading");
115 goto failed_to_submit_read;
116 }
117 if (memcmp("AVBf", page_address(page) + offset, 4) != 0) {
118 DMERR("invalidate_vbmeta called on non-vbmeta partition");
119 ret = -EINVAL;
120 goto invalid_header;
121 }
122 /* Stamp it. */
123 memcpy(page_address(page) + offset, "AVE0", 4);
124 DMINFO("invalidate_vbmeta: found vbmeta footer partition");
125 }
126
127 /* Now rewrite the changed page - the block dev was being
128 * changed on read. Let's reopen here.
129 */
130 blkdev_put(bdev, dev_mode);
131 dev_mode = FMODE_WRITE | FMODE_EXCL;
132 bdev = blkdev_get_by_dev(vbmeta_devt, dev_mode,
133 invalidate_vbmeta);
134 if (IS_ERR(bdev)) {
135 DMERR("invalidate_vbmeta: could not open device for writing");
136 dev_mode = 0;
137 ret = -ENOENT;
138 goto failed_to_write;
139 }
140
141 /* We re-use the same bio to do the write after the read. Need to reset
142 * it to initialize bio->bi_remaining.
143 */
144 bio_reset(bio);
145
146 rw |= REQ_WRITE;
147 ret = invalidate_vbmeta_submit(bio, bdev, rw, access_last_sector, page);
148 if (ret) {
149 DMERR("invalidate_vbmeta: error writing");
150 goto failed_to_submit_write;
151 }
152
153 DMERR("invalidate_vbmeta: completed.");
154 ret = 0;
155failed_to_submit_write:
156failed_to_write:
157invalid_header:
158 __free_page(page);
159failed_to_submit_read:
160 /* Technically, we'll leak a page with the pending bio, but
161 * we're about to reboot anyway.
162 */
163failed_to_alloc_page:
164 bio_put(bio);
165failed_bio_alloc:
166 if (dev_mode)
167 blkdev_put(bdev, dev_mode);
168failed_to_read:
169 return ret;
170}
171
172void dm_verity_avb_error_handler(void)
173{
174 dev_t dev;
175
176 DMINFO("AVB error handler called for %s", avb_vbmeta_device);
177
178 if (avb_vbmeta_device[0] == '\0') {
179 DMERR("avb_vbmeta_device parameter not set");
180 goto fail_no_dev;
181 }
182
183 dev = name_to_dev_t(avb_vbmeta_device);
184 if (!dev) {
185 DMERR("No matching partition for device: %s",
186 avb_vbmeta_device);
187 goto fail_no_dev;
188 }
189
190 invalidate_vbmeta(dev);
191
192fail_no_dev:
193 ;
194}
195
196static int __init dm_verity_avb_init(void)
197{
198 DMINFO("AVB error handler initialized with vbmeta device: %s",
199 avb_vbmeta_device);
200 return 0;
201}
202
203static void __exit dm_verity_avb_exit(void)
204{
205}
206
207module_init(dm_verity_avb_init);
208module_exit(dm_verity_avb_exit);
209
210MODULE_AUTHOR("David Zeuthen <zeuthen@google.com>");
211MODULE_DESCRIPTION("AVB-specific error handler for dm-verity");
212MODULE_LICENSE("GPL");
213
214/* Declare parameter with no module prefix */
215#undef MODULE_PARAM_PREFIX
216#define MODULE_PARAM_PREFIX "androidboot.vbmeta."
217module_param_string(device, avb_vbmeta_device, sizeof(avb_vbmeta_device), 0);