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