• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 parameters. */
16 static char avb_vbmeta_device[64];
17 static char avb_invalidate_on_error[4];
18 
invalidate_vbmeta_endio(struct bio * bio)19 static void invalidate_vbmeta_endio(struct bio *bio)
20 {
21 	if (bio->bi_status)
22 		DMERR("invalidate_vbmeta_endio: error %d", bio->bi_status);
23 	complete(bio->bi_private);
24 }
25 
invalidate_vbmeta_submit(struct bio * bio,struct block_device * bdev,int op,int access_last_sector,struct page * page)26 static 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_set_dev(bio, bdev);
36 	bio_set_op_attrs(bio, op, REQ_SYNC);
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 
invalidate_vbmeta(dev_t vbmeta_devt)57 static 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;
160 failed_to_submit_write:
161 failed_to_write:
162 invalid_header:
163 	__free_page(page);
164 failed_to_submit_read:
165 	/* Technically, we'll leak a page with the pending bio, but
166 	 * we're about to reboot anyway.
167 	 */
168 failed_to_alloc_page:
169 	bio_put(bio);
170 failed_bio_alloc:
171 	if (dev_mode)
172 		blkdev_put(bdev, dev_mode);
173 failed_to_read:
174 	return ret;
175 }
176 
dm_verity_avb_error_handler(void)177 void 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 
202 fail_no_dev:
203 	;
204 }
205 
dm_verity_avb_init(void)206 static 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 
dm_verity_avb_exit(void)213 static void __exit dm_verity_avb_exit(void)
214 {
215 }
216 
217 module_init(dm_verity_avb_init);
218 module_exit(dm_verity_avb_exit);
219 
220 MODULE_AUTHOR("David Zeuthen <zeuthen@google.com>");
221 MODULE_DESCRIPTION("AVB-specific error handler for dm-verity");
222 MODULE_LICENSE("GPL");
223 
224 /* Declare parameter with no module prefix */
225 #undef MODULE_PARAM_PREFIX
226 #define MODULE_PARAM_PREFIX	"androidboot.vbmeta."
227 module_param_string(device, avb_vbmeta_device, sizeof(avb_vbmeta_device), 0);
228 module_param_string(invalidate_on_error, avb_invalidate_on_error,
229 		    sizeof(avb_invalidate_on_error), 0);
230