blob: a2b739626b52437029697ef23882f5fd99998d1a [file] [log] [blame]
Igor Opaniuk3af30e42018-06-03 21:56:38 +03001/*
2 * (C) Copyright 2018, Linaro Limited
3 *
4 * SPDX-License-Identifier: GPL-2.0+
5 */
6
7#include <avb_verify.h>
Igor Opaniuke9ee7392018-07-17 14:33:26 +03008#include <blk.h>
Simon Glass1eb69ae2019-11-14 12:57:39 -07009#include <cpu_func.h>
Igor Opaniuk3af30e42018-06-03 21:56:38 +030010#include <fastboot.h>
11#include <image.h>
12#include <malloc.h>
13#include <part.h>
Jens Wiklander6663e072018-09-25 16:40:20 +020014#include <tee.h>
15#include <tee/optee_ta_avb.h>
Igor Opaniuk3af30e42018-06-03 21:56:38 +030016
Eugeniu Rosca55d56d22018-08-14 02:43:06 +020017static const unsigned char avb_root_pub[1032] = {
Igor Opaniuk3af30e42018-06-03 21:56:38 +030018 0x0, 0x0, 0x10, 0x0, 0x55, 0xd9, 0x4, 0xad, 0xd8, 0x4,
19 0xaf, 0xe3, 0xd3, 0x84, 0x6c, 0x7e, 0xd, 0x89, 0x3d, 0xc2,
20 0x8c, 0xd3, 0x12, 0x55, 0xe9, 0x62, 0xc9, 0xf1, 0xf, 0x5e,
21 0xcc, 0x16, 0x72, 0xab, 0x44, 0x7c, 0x2c, 0x65, 0x4a, 0x94,
22 0xb5, 0x16, 0x2b, 0x0, 0xbb, 0x6, 0xef, 0x13, 0x7, 0x53,
23 0x4c, 0xf9, 0x64, 0xb9, 0x28, 0x7a, 0x1b, 0x84, 0x98, 0x88,
24 0xd8, 0x67, 0xa4, 0x23, 0xf9, 0xa7, 0x4b, 0xdc, 0x4a, 0xf,
25 0xf7, 0x3a, 0x18, 0xae, 0x54, 0xa8, 0x15, 0xfe, 0xb0, 0xad,
26 0xac, 0x35, 0xda, 0x3b, 0xad, 0x27, 0xbc, 0xaf, 0xe8, 0xd3,
27 0x2f, 0x37, 0x34, 0xd6, 0x51, 0x2b, 0x6c, 0x5a, 0x27, 0xd7,
28 0x96, 0x6, 0xaf, 0x6b, 0xb8, 0x80, 0xca, 0xfa, 0x30, 0xb4,
29 0xb1, 0x85, 0xb3, 0x4d, 0xaa, 0xaa, 0xc3, 0x16, 0x34, 0x1a,
30 0xb8, 0xe7, 0xc7, 0xfa, 0xf9, 0x9, 0x77, 0xab, 0x97, 0x93,
31 0xeb, 0x44, 0xae, 0xcf, 0x20, 0xbc, 0xf0, 0x80, 0x11, 0xdb,
32 0x23, 0xc, 0x47, 0x71, 0xb9, 0x6d, 0xd6, 0x7b, 0x60, 0x47,
33 0x87, 0x16, 0x56, 0x93, 0xb7, 0xc2, 0x2a, 0x9a, 0xb0, 0x4c,
34 0x1, 0xc, 0x30, 0xd8, 0x93, 0x87, 0xf0, 0xed, 0x6e, 0x8b,
35 0xbe, 0x30, 0x5b, 0xf6, 0xa6, 0xaf, 0xdd, 0x80, 0x7c, 0x45,
36 0x5e, 0x8f, 0x91, 0x93, 0x5e, 0x44, 0xfe, 0xb8, 0x82, 0x7,
37 0xee, 0x79, 0xca, 0xbf, 0x31, 0x73, 0x62, 0x58, 0xe3, 0xcd,
38 0xc4, 0xbc, 0xc2, 0x11, 0x1d, 0xa1, 0x4a, 0xbf, 0xfe, 0x27,
39 0x7d, 0xa1, 0xf6, 0x35, 0xa3, 0x5e, 0xca, 0xdc, 0x57, 0x2f,
40 0x3e, 0xf0, 0xc9, 0x5d, 0x86, 0x6a, 0xf8, 0xaf, 0x66, 0xa7,
41 0xed, 0xcd, 0xb8, 0xed, 0xa1, 0x5f, 0xba, 0x9b, 0x85, 0x1a,
42 0xd5, 0x9, 0xae, 0x94, 0x4e, 0x3b, 0xcf, 0xcb, 0x5c, 0xc9,
43 0x79, 0x80, 0xf7, 0xcc, 0xa6, 0x4a, 0xa8, 0x6a, 0xd8, 0xd3,
44 0x31, 0x11, 0xf9, 0xf6, 0x2, 0x63, 0x2a, 0x1a, 0x2d, 0xd1,
45 0x1a, 0x66, 0x1b, 0x16, 0x41, 0xbd, 0xbd, 0xf7, 0x4d, 0xc0,
46 0x4a, 0xe5, 0x27, 0x49, 0x5f, 0x7f, 0x58, 0xe3, 0x27, 0x2d,
47 0xe5, 0xc9, 0x66, 0xe, 0x52, 0x38, 0x16, 0x38, 0xfb, 0x16,
48 0xeb, 0x53, 0x3f, 0xe6, 0xfd, 0xe9, 0xa2, 0x5e, 0x25, 0x59,
49 0xd8, 0x79, 0x45, 0xff, 0x3, 0x4c, 0x26, 0xa2, 0x0, 0x5a,
50 0x8e, 0xc2, 0x51, 0xa1, 0x15, 0xf9, 0x7b, 0xf4, 0x5c, 0x81,
51 0x9b, 0x18, 0x47, 0x35, 0xd8, 0x2d, 0x5, 0xe9, 0xad, 0xf,
52 0x35, 0x74, 0x15, 0xa3, 0x8e, 0x8b, 0xcc, 0x27, 0xda, 0x7c,
53 0x5d, 0xe4, 0xfa, 0x4, 0xd3, 0x5, 0xb, 0xba, 0x3a, 0xb2,
54 0x49, 0x45, 0x2f, 0x47, 0xc7, 0xd, 0x41, 0x3f, 0x97, 0x80,
55 0x4d, 0x3f, 0xc1, 0xb5, 0xbb, 0x70, 0x5f, 0xa7, 0x37, 0xaf,
56 0x48, 0x22, 0x12, 0x45, 0x2e, 0xf5, 0xf, 0x87, 0x92, 0xe2,
57 0x84, 0x1, 0xf9, 0x12, 0xf, 0x14, 0x15, 0x24, 0xce, 0x89,
58 0x99, 0xee, 0xb9, 0xc4, 0x17, 0x70, 0x70, 0x15, 0xea, 0xbe,
59 0xc6, 0x6c, 0x1f, 0x62, 0xb3, 0xf4, 0x2d, 0x16, 0x87, 0xfb,
60 0x56, 0x1e, 0x45, 0xab, 0xae, 0x32, 0xe4, 0x5e, 0x91, 0xed,
61 0x53, 0x66, 0x5e, 0xbd, 0xed, 0xad, 0xe6, 0x12, 0x39, 0xd,
62 0x83, 0xc9, 0xe8, 0x6b, 0x6c, 0x2d, 0xa5, 0xee, 0xc4, 0x5a,
63 0x66, 0xae, 0x8c, 0x97, 0xd7, 0xd, 0x6c, 0x49, 0xc7, 0xf5,
64 0xc4, 0x92, 0x31, 0x8b, 0x9, 0xee, 0x33, 0xda, 0xa9, 0x37,
65 0xb6, 0x49, 0x18, 0xf8, 0xe, 0x60, 0x45, 0xc8, 0x33, 0x91,
66 0xef, 0x20, 0x57, 0x10, 0xbe, 0x78, 0x2d, 0x83, 0x26, 0xd6,
67 0xca, 0x61, 0xf9, 0x2f, 0xe0, 0xbf, 0x5, 0x30, 0x52, 0x5a,
68 0x12, 0x1c, 0x0, 0xa7, 0x5d, 0xcc, 0x7c, 0x2e, 0xc5, 0x95,
69 0x8b, 0xa3, 0x3b, 0xf0, 0x43, 0x2e, 0x5e, 0xdd, 0x0, 0xdb,
70 0xd, 0xb3, 0x37, 0x99, 0xa9, 0xcd, 0x9c, 0xb7, 0x43, 0xf7,
71 0x35, 0x44, 0x21, 0xc2, 0x82, 0x71, 0xab, 0x8d, 0xaa, 0xb4,
72 0x41, 0x11, 0xec, 0x1e, 0x8d, 0xfc, 0x14, 0x82, 0x92, 0x4e,
73 0x83, 0x6a, 0xa, 0x6b, 0x35, 0x5e, 0x5d, 0xe9, 0x5c, 0xcc,
74 0x8c, 0xde, 0x39, 0xd1, 0x4a, 0x5b, 0x5f, 0x63, 0xa9, 0x64,
75 0xe0, 0xa, 0xcb, 0xb, 0xb8, 0x5a, 0x7c, 0xc3, 0xb, 0xe6,
76 0xbe, 0xfe, 0x8b, 0xf, 0x7d, 0x34, 0x8e, 0x2, 0x66, 0x74,
77 0x1, 0x6c, 0xca, 0x76, 0xac, 0x7c, 0x67, 0x8, 0x2f, 0x3f,
78 0x1a, 0xa6, 0x2c, 0x60, 0xb3, 0xff, 0xda, 0x8d, 0xb8, 0x12,
79 0xc, 0x0, 0x7f, 0xcc, 0x50, 0xa1, 0x5c, 0x64, 0xa1, 0xe2,
80 0x5f, 0x32, 0x65, 0xc9, 0x9c, 0xbe, 0xd6, 0xa, 0x13, 0x87,
81 0x3c, 0x2a, 0x45, 0x47, 0xc, 0xca, 0x42, 0x82, 0xfa, 0x89,
82 0x65, 0xe7, 0x89, 0xb4, 0x8f, 0xf7, 0x1e, 0xe6, 0x23, 0xa5,
83 0xd0, 0x59, 0x37, 0x79, 0x92, 0xd7, 0xce, 0x3d, 0xfd, 0xe3,
84 0xa1, 0xb, 0xcf, 0x6c, 0x85, 0xa0, 0x65, 0xf3, 0x5c, 0xc6,
85 0x4a, 0x63, 0x5f, 0x6e, 0x3a, 0x3a, 0x2a, 0x8b, 0x6a, 0xb6,
86 0x2f, 0xbb, 0xf8, 0xb2, 0x4b, 0x62, 0xbc, 0x1a, 0x91, 0x25,
87 0x66, 0xe3, 0x69, 0xca, 0x60, 0x49, 0xb, 0xf6, 0x8a, 0xbe,
88 0x3e, 0x76, 0x53, 0xc2, 0x7a, 0xa8, 0x4, 0x17, 0x75, 0xf1,
89 0xf3, 0x3, 0x62, 0x1b, 0x85, 0xb2, 0xb0, 0xef, 0x80, 0x15,
90 0xb6, 0xd4, 0x4e, 0xdf, 0x71, 0xac, 0xdb, 0x2a, 0x4, 0xd4,
91 0xb4, 0x21, 0xba, 0x65, 0x56, 0x57, 0xe8, 0xfa, 0x84, 0xa2,
92 0x7d, 0x13, 0xe, 0xaf, 0xd7, 0x9a, 0x58, 0x2a, 0xa3, 0x81,
93 0x84, 0x8d, 0x9, 0xa0, 0x6a, 0xc1, 0xbb, 0xd9, 0xf5, 0x86,
94 0xac, 0xbd, 0x75, 0x61, 0x9, 0xe6, 0x8c, 0x3d, 0x77, 0xb2,
95 0xed, 0x30, 0x20, 0xe4, 0x0, 0x1d, 0x97, 0xe8, 0xbf, 0xc7,
96 0x0, 0x1b, 0x21, 0xb1, 0x16, 0xe7, 0x41, 0x67, 0x2e, 0xec,
97 0x38, 0xbc, 0xe5, 0x1b, 0xb4, 0x6, 0x23, 0x31, 0x71, 0x1c,
98 0x49, 0xcd, 0x76, 0x4a, 0x76, 0x36, 0x8d, 0xa3, 0x89, 0x8b,
99 0x4a, 0x7a, 0xf4, 0x87, 0xc8, 0x15, 0xf, 0x37, 0x39, 0xf6,
100 0x6d, 0x80, 0x19, 0xef, 0x5c, 0xa8, 0x66, 0xce, 0x1b, 0x16,
101 0x79, 0x21, 0xdf, 0xd7, 0x31, 0x30, 0xc4, 0x21, 0xdd, 0x34,
102 0x5b, 0xd2, 0x1a, 0x2b, 0x3e, 0x5d, 0xf7, 0xea, 0xca, 0x5,
103 0x8e, 0xb7, 0xcb, 0x49, 0x2e, 0xa0, 0xe3, 0xf4, 0xa7, 0x48,
104 0x19, 0x10, 0x9c, 0x4, 0xa7, 0xf4, 0x28, 0x74, 0xc8, 0x6f,
105 0x63, 0x20, 0x2b, 0x46, 0x24, 0x26, 0x19, 0x1d, 0xd1, 0x2c,
106 0x31, 0x6d, 0x5a, 0x29, 0xa2, 0x6, 0xa6, 0xb2, 0x41, 0xcc,
107 0xa, 0x27, 0x96, 0x9, 0x96, 0xac, 0x47, 0x65, 0x78, 0x68,
108 0x51, 0x98, 0xd6, 0xd8, 0xa6, 0x2d, 0xa0, 0xcf, 0xec, 0xe2,
109 0x74, 0xf2, 0x82, 0xe3, 0x97, 0xd9, 0x7e, 0xd4, 0xf8, 0xb,
110 0x70, 0x43, 0x3d, 0xb1, 0x7b, 0x97, 0x80, 0xd6, 0xcb, 0xd7,
111 0x19, 0xbc, 0x63, 0xb, 0xfd, 0x4d, 0x88, 0xfe, 0x67, 0xac,
112 0xb8, 0xcc, 0x50, 0xb7, 0x68, 0xb3, 0x5b, 0xd6, 0x1e, 0x25,
113 0xfc, 0x5f, 0x3c, 0x8d, 0xb1, 0x33, 0x7c, 0xb3, 0x49, 0x1,
114 0x3f, 0x71, 0x55, 0xe, 0x51, 0xba, 0x61, 0x26, 0xfa, 0xea,
115 0xe5, 0xb5, 0xe8, 0xaa, 0xcf, 0xcd, 0x96, 0x9f, 0xd6, 0xc1,
116 0x5f, 0x53, 0x91, 0xad, 0x5, 0xde, 0x20, 0xe7, 0x51, 0xda,
117 0x5b, 0x95, 0x67, 0xed, 0xf4, 0xee, 0x42, 0x65, 0x70, 0x13,
118 0xb, 0x70, 0x14, 0x1c, 0xc9, 0xe0, 0x19, 0xca, 0x5f, 0xf5,
119 0x1d, 0x70, 0x4b, 0x6c, 0x6, 0x74, 0xec, 0xb5, 0x2e, 0x77,
120 0xe1, 0x74, 0xa1, 0xa3, 0x99, 0xa0, 0x85, 0x9e, 0xf1, 0xac,
121 0xd8, 0x7e,
122};
123
124/**
125 * ============================================================================
Igor Opaniuk5d4fd872018-06-03 21:56:40 +0300126 * Boot states support (GREEN, YELLOW, ORANGE, RED) and dm_verity
127 * ============================================================================
128 */
129char *avb_set_state(AvbOps *ops, enum avb_boot_state boot_state)
130{
131 struct AvbOpsData *data;
132 char *cmdline = NULL;
133
134 if (!ops)
135 return NULL;
136
137 data = (struct AvbOpsData *)ops->user_data;
138 if (!data)
139 return NULL;
140
141 data->boot_state = boot_state;
142 switch (boot_state) {
143 case AVB_GREEN:
144 cmdline = "androidboot.verifiedbootstate=green";
145 break;
146 case AVB_YELLOW:
147 cmdline = "androidboot.verifiedbootstate=yellow";
148 break;
149 case AVB_ORANGE:
150 cmdline = "androidboot.verifiedbootstate=orange";
151 case AVB_RED:
152 break;
153 }
154
155 return cmdline;
156}
157
158char *append_cmd_line(char *cmdline_orig, char *cmdline_new)
159{
160 char *cmd_line;
161
162 if (!cmdline_new)
163 return cmdline_orig;
164
165 if (cmdline_orig)
166 cmd_line = cmdline_orig;
167 else
168 cmd_line = " ";
169
170 cmd_line = avb_strdupv(cmd_line, " ", cmdline_new, NULL);
171
172 return cmd_line;
173}
174
175static int avb_find_dm_args(char **args, char *str)
176{
177 int i;
178
179 if (!str)
180 return -1;
181
Eugeniu Rosca2e2067b2018-08-14 02:43:04 +0200182 for (i = 0; i < AVB_MAX_ARGS && args[i]; ++i) {
Igor Opaniuk5d4fd872018-06-03 21:56:40 +0300183 if (strstr(args[i], str))
184 return i;
185 }
186
187 return -1;
188}
189
190static char *avb_set_enforce_option(const char *cmdline, const char *option)
191{
192 char *cmdarg[AVB_MAX_ARGS];
193 char *newargs = NULL;
194 int i = 0;
195 int total_args;
196
197 memset(cmdarg, 0, sizeof(cmdarg));
198 cmdarg[i++] = strtok((char *)cmdline, " ");
199
200 do {
201 cmdarg[i] = strtok(NULL, " ");
202 if (!cmdarg[i])
203 break;
204
205 if (++i >= AVB_MAX_ARGS) {
206 printf("%s: Can't handle more then %d args\n",
207 __func__, i);
208 return NULL;
209 }
210 } while (true);
211
212 total_args = i;
213 i = avb_find_dm_args(&cmdarg[0], VERITY_TABLE_OPT_LOGGING);
214 if (i >= 0) {
215 cmdarg[i] = (char *)option;
216 } else {
217 i = avb_find_dm_args(&cmdarg[0], VERITY_TABLE_OPT_RESTART);
218 if (i < 0) {
219 printf("%s: No verity options found\n", __func__);
220 return NULL;
221 }
222
223 cmdarg[i] = (char *)option;
224 }
225
226 for (i = 0; i <= total_args; i++)
227 newargs = append_cmd_line(newargs, cmdarg[i]);
228
229 return newargs;
230}
231
232char *avb_set_ignore_corruption(const char *cmdline)
233{
234 char *newargs = NULL;
235
236 newargs = avb_set_enforce_option(cmdline, VERITY_TABLE_OPT_LOGGING);
237 if (newargs)
238 newargs = append_cmd_line(newargs,
239 "androidboot.veritymode=eio");
240
241 return newargs;
242}
243
244char *avb_set_enforce_verity(const char *cmdline)
245{
246 char *newargs;
247
248 newargs = avb_set_enforce_option(cmdline, VERITY_TABLE_OPT_RESTART);
249 if (newargs)
250 newargs = append_cmd_line(newargs,
251 "androidboot.veritymode=enforcing");
252 return newargs;
253}
254
255/**
256 * ============================================================================
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300257 * IO(mmc) auxiliary functions
258 * ============================================================================
259 */
260static unsigned long mmc_read_and_flush(struct mmc_part *part,
261 lbaint_t start,
262 lbaint_t sectors,
263 void *buffer)
264{
265 unsigned long blks;
266 void *tmp_buf;
267 size_t buf_size;
268 bool unaligned = is_buf_unaligned(buffer);
269
270 if (start < part->info.start) {
271 printf("%s: partition start out of bounds\n", __func__);
272 return 0;
273 }
274 if ((start + sectors) > (part->info.start + part->info.size)) {
275 sectors = part->info.start + part->info.size - start;
276 printf("%s: read sector aligned to partition bounds (%ld)\n",
277 __func__, sectors);
278 }
279
280 /*
281 * Reading fails on unaligned buffers, so we have to
282 * use aligned temporary buffer and then copy to destination
283 */
284
285 if (unaligned) {
286 printf("Handling unaligned read buffer..\n");
287 tmp_buf = get_sector_buf();
288 buf_size = get_sector_buf_size();
289 if (sectors > buf_size / part->info.blksz)
290 sectors = buf_size / part->info.blksz;
291 } else {
292 tmp_buf = buffer;
293 }
294
Igor Opaniuke9ee7392018-07-17 14:33:26 +0300295 blks = blk_dread(part->mmc_blk,
296 start, sectors, tmp_buf);
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300297 /* flush cache after read */
298 flush_cache((ulong)tmp_buf, sectors * part->info.blksz);
299
300 if (unaligned)
301 memcpy(buffer, tmp_buf, sectors * part->info.blksz);
302
303 return blks;
304}
305
306static unsigned long mmc_write(struct mmc_part *part, lbaint_t start,
307 lbaint_t sectors, void *buffer)
308{
309 void *tmp_buf;
310 size_t buf_size;
311 bool unaligned = is_buf_unaligned(buffer);
312
313 if (start < part->info.start) {
314 printf("%s: partition start out of bounds\n", __func__);
315 return 0;
316 }
317 if ((start + sectors) > (part->info.start + part->info.size)) {
318 sectors = part->info.start + part->info.size - start;
319 printf("%s: sector aligned to partition bounds (%ld)\n",
320 __func__, sectors);
321 }
322 if (unaligned) {
323 tmp_buf = get_sector_buf();
324 buf_size = get_sector_buf_size();
325 printf("Handling unaligned wrire buffer..\n");
326 if (sectors > buf_size / part->info.blksz)
327 sectors = buf_size / part->info.blksz;
328
329 memcpy(tmp_buf, buffer, sectors * part->info.blksz);
330 } else {
331 tmp_buf = buffer;
332 }
333
Igor Opaniuke9ee7392018-07-17 14:33:26 +0300334 return blk_dwrite(part->mmc_blk,
335 start, sectors, tmp_buf);
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300336}
337
338static struct mmc_part *get_partition(AvbOps *ops, const char *partition)
339{
340 int ret;
341 u8 dev_num;
342 int part_num = 0;
343 struct mmc_part *part;
344 struct blk_desc *mmc_blk;
345
346 part = malloc(sizeof(struct mmc_part));
347 if (!part)
348 return NULL;
349
350 dev_num = get_boot_device(ops);
351 part->mmc = find_mmc_device(dev_num);
352 if (!part->mmc) {
353 printf("No MMC device at slot %x\n", dev_num);
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200354 goto err;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300355 }
356
357 if (mmc_init(part->mmc)) {
358 printf("MMC initialization failed\n");
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200359 goto err;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300360 }
361
362 ret = mmc_switch_part(part->mmc, part_num);
363 if (ret)
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200364 goto err;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300365
366 mmc_blk = mmc_get_blk_desc(part->mmc);
367 if (!mmc_blk) {
368 printf("Error - failed to obtain block descriptor\n");
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200369 goto err;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300370 }
371
372 ret = part_get_info_by_name(mmc_blk, partition, &part->info);
373 if (!ret) {
374 printf("Can't find partition '%s'\n", partition);
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200375 goto err;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300376 }
377
378 part->dev_num = dev_num;
379 part->mmc_blk = mmc_blk;
380
381 return part;
Eugeniu Rosca047bc5c2018-08-14 02:43:07 +0200382err:
383 free(part);
384 return NULL;
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300385}
386
387static AvbIOResult mmc_byte_io(AvbOps *ops,
388 const char *partition,
389 s64 offset,
390 size_t num_bytes,
391 void *buffer,
392 size_t *out_num_read,
393 enum mmc_io_type io_type)
394{
395 ulong ret;
396 struct mmc_part *part;
397 u64 start_offset, start_sector, sectors, residue;
398 u8 *tmp_buf;
399 size_t io_cnt = 0;
400
401 if (!partition || !buffer || io_type > IO_WRITE)
402 return AVB_IO_RESULT_ERROR_IO;
403
404 part = get_partition(ops, partition);
405 if (!part)
406 return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
407
Eugeniu Roscae1904f42018-08-14 02:43:09 +0200408 if (!part->info.blksz)
409 return AVB_IO_RESULT_ERROR_IO;
410
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300411 start_offset = calc_offset(part, offset);
412 while (num_bytes) {
413 start_sector = start_offset / part->info.blksz;
414 sectors = num_bytes / part->info.blksz;
415 /* handle non block-aligned reads */
416 if (start_offset % part->info.blksz ||
417 num_bytes < part->info.blksz) {
418 tmp_buf = get_sector_buf();
419 if (start_offset % part->info.blksz) {
420 residue = part->info.blksz -
421 (start_offset % part->info.blksz);
422 if (residue > num_bytes)
423 residue = num_bytes;
424 } else {
425 residue = num_bytes;
426 }
427
428 if (io_type == IO_READ) {
429 ret = mmc_read_and_flush(part,
430 part->info.start +
431 start_sector,
432 1, tmp_buf);
433
434 if (ret != 1) {
435 printf("%s: read error (%ld, %lld)\n",
436 __func__, ret, start_sector);
437 return AVB_IO_RESULT_ERROR_IO;
438 }
439 /*
440 * if this is not aligned at sector start,
441 * we have to adjust the tmp buffer
442 */
443 tmp_buf += (start_offset % part->info.blksz);
444 memcpy(buffer, (void *)tmp_buf, residue);
445 } else {
446 ret = mmc_read_and_flush(part,
447 part->info.start +
448 start_sector,
449 1, tmp_buf);
450
451 if (ret != 1) {
452 printf("%s: read error (%ld, %lld)\n",
453 __func__, ret, start_sector);
454 return AVB_IO_RESULT_ERROR_IO;
455 }
456 memcpy((void *)tmp_buf +
457 start_offset % part->info.blksz,
458 buffer, residue);
459
460 ret = mmc_write(part, part->info.start +
461 start_sector, 1, tmp_buf);
462 if (ret != 1) {
463 printf("%s: write error (%ld, %lld)\n",
464 __func__, ret, start_sector);
465 return AVB_IO_RESULT_ERROR_IO;
466 }
467 }
468
469 io_cnt += residue;
470 buffer += residue;
471 start_offset += residue;
472 num_bytes -= residue;
473 continue;
474 }
475
476 if (sectors) {
477 if (io_type == IO_READ) {
478 ret = mmc_read_and_flush(part,
479 part->info.start +
480 start_sector,
481 sectors, buffer);
482 } else {
483 ret = mmc_write(part,
484 part->info.start +
485 start_sector,
486 sectors, buffer);
487 }
488
489 if (!ret) {
490 printf("%s: sector read error\n", __func__);
491 return AVB_IO_RESULT_ERROR_IO;
492 }
493
494 io_cnt += ret * part->info.blksz;
495 buffer += ret * part->info.blksz;
496 start_offset += ret * part->info.blksz;
497 num_bytes -= ret * part->info.blksz;
498 }
499 }
500
501 /* Set counter for read operation */
502 if (io_type == IO_READ && out_num_read)
503 *out_num_read = io_cnt;
504
505 return AVB_IO_RESULT_OK;
506}
507
508/**
509 * ============================================================================
510 * AVB 2.0 operations
511 * ============================================================================
512 */
513
514/**
515 * read_from_partition() - reads @num_bytes from @offset from partition
516 * identified by a string name
517 *
518 * @ops: contains AVB ops handlers
519 * @partition_name: partition name, NUL-terminated UTF-8 string
520 * @offset: offset from the beginning of partition
521 * @num_bytes: amount of bytes to read
522 * @buffer: destination buffer to store data
523 * @out_num_read:
524 *
525 * @return:
526 * AVB_IO_RESULT_OK, if partition was found and read operation succeed
527 * AVB_IO_RESULT_ERROR_IO, if i/o error occurred from the underlying i/o
528 * subsystem
529 * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, if there is no partition with
530 * the given name
531 */
532static AvbIOResult read_from_partition(AvbOps *ops,
533 const char *partition_name,
534 s64 offset_from_partition,
535 size_t num_bytes,
536 void *buffer,
537 size_t *out_num_read)
538{
539 return mmc_byte_io(ops, partition_name, offset_from_partition,
540 num_bytes, buffer, out_num_read, IO_READ);
541}
542
543/**
544 * write_to_partition() - writes N bytes to a partition identified by a string
545 * name
546 *
547 * @ops: AvbOps, contains AVB ops handlers
548 * @partition_name: partition name
549 * @offset_from_partition: offset from the beginning of partition
550 * @num_bytes: amount of bytes to write
551 * @buf: data to write
552 * @out_num_read:
553 *
554 * @return:
555 * AVB_IO_RESULT_OK, if partition was found and read operation succeed
556 * AVB_IO_RESULT_ERROR_IO, if input/output error occurred
557 * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, if partition, specified in
558 * @partition_name was not found
559 */
560static AvbIOResult write_to_partition(AvbOps *ops,
561 const char *partition_name,
562 s64 offset_from_partition,
563 size_t num_bytes,
564 const void *buffer)
565{
566 return mmc_byte_io(ops, partition_name, offset_from_partition,
567 num_bytes, (void *)buffer, NULL, IO_WRITE);
568}
569
570/**
571 * validate_vmbeta_public_key() - checks if the given public key used to sign
572 * the vbmeta partition is trusted
573 *
574 * @ops: AvbOps, contains AVB ops handlers
575 * @public_key_data: public key for verifying vbmeta partition signature
576 * @public_key_length: length of public key
577 * @public_key_metadata:
578 * @public_key_metadata_length:
579 * @out_key_is_trusted:
580 *
581 * @return:
582 * AVB_IO_RESULT_OK, if partition was found and read operation succeed
583 */
584static AvbIOResult validate_vbmeta_public_key(AvbOps *ops,
585 const u8 *public_key_data,
586 size_t public_key_length,
587 const u8
588 *public_key_metadata,
589 size_t
590 public_key_metadata_length,
591 bool *out_key_is_trusted)
592{
593 if (!public_key_length || !public_key_data || !out_key_is_trusted)
594 return AVB_IO_RESULT_ERROR_IO;
595
596 *out_key_is_trusted = false;
597 if (public_key_length != sizeof(avb_root_pub))
598 return AVB_IO_RESULT_ERROR_IO;
599
600 if (memcmp(avb_root_pub, public_key_data, public_key_length) == 0)
601 *out_key_is_trusted = true;
602
603 return AVB_IO_RESULT_OK;
604}
605
Jens Wiklander6663e072018-09-25 16:40:20 +0200606#ifdef CONFIG_OPTEE_TA_AVB
607static int get_open_session(struct AvbOpsData *ops_data)
608{
609 struct udevice *tee = NULL;
610
611 while (!ops_data->tee) {
612 const struct tee_optee_ta_uuid uuid = TA_AVB_UUID;
613 struct tee_open_session_arg arg;
614 int rc;
615
616 tee = tee_find_device(tee, NULL, NULL, NULL);
617 if (!tee)
618 return -ENODEV;
619
620 memset(&arg, 0, sizeof(arg));
621 tee_optee_ta_uuid_to_octets(arg.uuid, &uuid);
622 rc = tee_open_session(tee, &arg, 0, NULL);
623 if (!rc) {
624 ops_data->tee = tee;
625 ops_data->session = arg.session;
626 }
627 }
628
629 return 0;
630}
631
632static AvbIOResult invoke_func(struct AvbOpsData *ops_data, u32 func,
633 ulong num_param, struct tee_param *param)
634{
635 struct tee_invoke_arg arg;
636
637 if (get_open_session(ops_data))
638 return AVB_IO_RESULT_ERROR_IO;
639
640 memset(&arg, 0, sizeof(arg));
641 arg.func = func;
642 arg.session = ops_data->session;
643
644 if (tee_invoke_func(ops_data->tee, &arg, num_param, param))
645 return AVB_IO_RESULT_ERROR_IO;
646 switch (arg.ret) {
647 case TEE_SUCCESS:
648 return AVB_IO_RESULT_OK;
649 case TEE_ERROR_OUT_OF_MEMORY:
650 return AVB_IO_RESULT_ERROR_OOM;
Igor Opaniukfc1fe012019-04-09 15:38:14 +0200651 case TEE_ERROR_STORAGE_NO_SPACE:
652 return AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE;
653 case TEE_ERROR_ITEM_NOT_FOUND:
654 return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
Jens Wiklander6663e072018-09-25 16:40:20 +0200655 case TEE_ERROR_TARGET_DEAD:
656 /*
657 * The TA has paniced, close the session to reload the TA
658 * for the next request.
659 */
660 tee_close_session(ops_data->tee, ops_data->session);
661 ops_data->tee = NULL;
662 return AVB_IO_RESULT_ERROR_IO;
663 default:
664 return AVB_IO_RESULT_ERROR_IO;
665 }
666}
667#endif
668
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300669/**
670 * read_rollback_index() - gets the rollback index corresponding to the
671 * location of given by @out_rollback_index.
672 *
673 * @ops: contains AvbOps handlers
674 * @rollback_index_slot:
675 * @out_rollback_index: used to write a retrieved rollback index.
676 *
677 * @return
678 * AVB_IO_RESULT_OK, if the roolback index was retrieved
679 */
680static AvbIOResult read_rollback_index(AvbOps *ops,
681 size_t rollback_index_slot,
682 u64 *out_rollback_index)
683{
Jens Wiklander6663e072018-09-25 16:40:20 +0200684#ifndef CONFIG_OPTEE_TA_AVB
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300685 /* For now we always return 0 as the stored rollback index. */
Igor Opaniuk5d4fd872018-06-03 21:56:40 +0300686 printf("%s not supported yet\n", __func__);
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300687
688 if (out_rollback_index)
689 *out_rollback_index = 0;
690
691 return AVB_IO_RESULT_OK;
Jens Wiklander6663e072018-09-25 16:40:20 +0200692#else
693 AvbIOResult rc;
694 struct tee_param param[2];
695
696 if (rollback_index_slot >= TA_AVB_MAX_ROLLBACK_LOCATIONS)
697 return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
698
699 memset(param, 0, sizeof(param));
700 param[0].attr = TEE_PARAM_ATTR_TYPE_VALUE_INPUT;
701 param[0].u.value.a = rollback_index_slot;
702 param[1].attr = TEE_PARAM_ATTR_TYPE_VALUE_OUTPUT;
703
704 rc = invoke_func(ops->user_data, TA_AVB_CMD_READ_ROLLBACK_INDEX,
705 ARRAY_SIZE(param), param);
706 if (rc)
707 return rc;
708
709 *out_rollback_index = (u64)param[1].u.value.a << 32 |
710 (u32)param[1].u.value.b;
711 return AVB_IO_RESULT_OK;
712#endif
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300713}
714
715/**
716 * write_rollback_index() - sets the rollback index corresponding to the
717 * location of given by @out_rollback_index.
718 *
719 * @ops: contains AvbOps handlers
720 * @rollback_index_slot:
721 * @rollback_index: rollback index to write.
722 *
723 * @return
724 * AVB_IO_RESULT_OK, if the roolback index was retrieved
725 */
726static AvbIOResult write_rollback_index(AvbOps *ops,
727 size_t rollback_index_slot,
728 u64 rollback_index)
729{
Jens Wiklander6663e072018-09-25 16:40:20 +0200730#ifndef CONFIG_OPTEE_TA_AVB
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300731 /* For now this is a no-op. */
Igor Opaniuk5d4fd872018-06-03 21:56:40 +0300732 printf("%s not supported yet\n", __func__);
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300733
734 return AVB_IO_RESULT_OK;
Jens Wiklander6663e072018-09-25 16:40:20 +0200735#else
736 struct tee_param param[2];
737
738 if (rollback_index_slot >= TA_AVB_MAX_ROLLBACK_LOCATIONS)
739 return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
740
741 memset(param, 0, sizeof(param));
742 param[0].attr = TEE_PARAM_ATTR_TYPE_VALUE_INPUT;
743 param[0].u.value.a = rollback_index_slot;
744 param[1].attr = TEE_PARAM_ATTR_TYPE_VALUE_INPUT;
745 param[1].u.value.a = (u32)(rollback_index >> 32);
746 param[1].u.value.b = (u32)rollback_index;
747
748 return invoke_func(ops->user_data, TA_AVB_CMD_WRITE_ROLLBACK_INDEX,
749 ARRAY_SIZE(param), param);
750#endif
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300751}
752
753/**
754 * read_is_device_unlocked() - gets whether the device is unlocked
755 *
756 * @ops: contains AVB ops handlers
757 * @out_is_unlocked: device unlock state is stored here, true if unlocked,
758 * false otherwise
759 *
760 * @return:
761 * AVB_IO_RESULT_OK: state is retrieved successfully
762 * AVB_IO_RESULT_ERROR_IO: an error occurred
763 */
764static AvbIOResult read_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked)
765{
Jens Wiklander6663e072018-09-25 16:40:20 +0200766#ifndef CONFIG_OPTEE_TA_AVB
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300767 /* For now we always return that the device is unlocked. */
768
Igor Opaniuk5d4fd872018-06-03 21:56:40 +0300769 printf("%s not supported yet\n", __func__);
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300770
771 *out_is_unlocked = true;
772
773 return AVB_IO_RESULT_OK;
Jens Wiklander6663e072018-09-25 16:40:20 +0200774#else
775 AvbIOResult rc;
776 struct tee_param param = { .attr = TEE_PARAM_ATTR_TYPE_VALUE_OUTPUT };
777
778 rc = invoke_func(ops->user_data, TA_AVB_CMD_READ_LOCK_STATE, 1, &param);
779 if (rc)
780 return rc;
781 *out_is_unlocked = !param.u.value.a;
782 return AVB_IO_RESULT_OK;
783#endif
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300784}
785
786/**
787 * get_unique_guid_for_partition() - gets the GUID for a partition identified
788 * by a string name
789 *
790 * @ops: contains AVB ops handlers
791 * @partition: partition name (NUL-terminated UTF-8 string)
792 * @guid_buf: buf, used to copy in GUID string. Example of value:
793 * 527c1c6d-6361-4593-8842-3c78fcd39219
794 * @guid_buf_size: @guid_buf buffer size
795 *
796 * @return:
797 * AVB_IO_RESULT_OK, on success (GUID found)
798 * AVB_IO_RESULT_ERROR_IO, if incorrect buffer size (@guid_buf_size) was
799 * provided
800 * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, if partition was not found
801 */
802static AvbIOResult get_unique_guid_for_partition(AvbOps *ops,
803 const char *partition,
804 char *guid_buf,
805 size_t guid_buf_size)
806{
807 struct mmc_part *part;
808 size_t uuid_size;
809
810 part = get_partition(ops, partition);
811 if (!part)
812 return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
813
814 uuid_size = sizeof(part->info.uuid);
815 if (uuid_size > guid_buf_size)
816 return AVB_IO_RESULT_ERROR_IO;
817
818 memcpy(guid_buf, part->info.uuid, uuid_size);
819 guid_buf[uuid_size - 1] = 0;
820
821 return AVB_IO_RESULT_OK;
822}
823
824/**
Igor Opaniuk7a5fbfe2018-08-10 16:59:59 +0300825 * get_size_of_partition() - gets the size of a partition identified
826 * by a string name
827 *
828 * @ops: contains AVB ops handlers
829 * @partition: partition name (NUL-terminated UTF-8 string)
830 * @out_size_num_bytes: returns the value of a partition size
831 *
832 * @return:
833 * AVB_IO_RESULT_OK, on success (GUID found)
834 * AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE, out_size_num_bytes is NULL
835 * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, if partition was not found
836 */
837static AvbIOResult get_size_of_partition(AvbOps *ops,
838 const char *partition,
839 u64 *out_size_num_bytes)
840{
841 struct mmc_part *part;
842
843 if (!out_size_num_bytes)
844 return AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE;
845
846 part = get_partition(ops, partition);
847 if (!part)
848 return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
849
850 *out_size_num_bytes = part->info.blksz * part->info.size;
851
852 return AVB_IO_RESULT_OK;
853}
854
Sam Protsenkof254bd02019-07-31 19:59:08 +0300855#ifdef CONFIG_OPTEE_TA_AVB
Igor Opaniukfc1fe012019-04-09 15:38:14 +0200856static AvbIOResult read_persistent_value(AvbOps *ops,
857 const char *name,
858 size_t buffer_size,
859 u8 *out_buffer,
860 size_t *out_num_bytes_read)
861{
862 AvbIOResult rc;
863 struct tee_shm *shm_name;
864 struct tee_shm *shm_buf;
865 struct tee_param param[2];
866 struct udevice *tee;
867 size_t name_size = strlen(name) + 1;
868
869 if (get_open_session(ops->user_data))
870 return AVB_IO_RESULT_ERROR_IO;
871
872 tee = ((struct AvbOpsData *)ops->user_data)->tee;
873
874 rc = tee_shm_alloc(tee, name_size,
875 TEE_SHM_ALLOC, &shm_name);
876 if (rc)
877 return AVB_IO_RESULT_ERROR_OOM;
878
879 rc = tee_shm_alloc(tee, buffer_size,
880 TEE_SHM_ALLOC, &shm_buf);
881 if (rc) {
882 rc = AVB_IO_RESULT_ERROR_OOM;
883 goto free_name;
884 }
885
886 memcpy(shm_name->addr, name, name_size);
887
888 memset(param, 0, sizeof(param));
889 param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
890 param[0].u.memref.shm = shm_name;
891 param[0].u.memref.size = name_size;
892 param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INOUT;
893 param[1].u.memref.shm = shm_buf;
894 param[1].u.memref.size = buffer_size;
895
896 rc = invoke_func(ops->user_data, TA_AVB_CMD_READ_PERSIST_VALUE,
897 2, param);
898 if (rc)
899 goto out;
900
901 if (param[1].u.memref.size > buffer_size) {
902 rc = AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
903 goto out;
904 }
905
906 *out_num_bytes_read = param[1].u.memref.size;
907
908 memcpy(out_buffer, shm_buf->addr, *out_num_bytes_read);
909
910out:
911 tee_shm_free(shm_buf);
912free_name:
913 tee_shm_free(shm_name);
914
915 return rc;
916}
917
918static AvbIOResult write_persistent_value(AvbOps *ops,
919 const char *name,
920 size_t value_size,
921 const u8 *value)
922{
923 AvbIOResult rc;
924 struct tee_shm *shm_name;
925 struct tee_shm *shm_buf;
926 struct tee_param param[2];
927 struct udevice *tee;
928 size_t name_size = strlen(name) + 1;
929
930 if (get_open_session(ops->user_data))
931 return AVB_IO_RESULT_ERROR_IO;
932
933 tee = ((struct AvbOpsData *)ops->user_data)->tee;
934
935 if (!value_size)
936 return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
937
938 rc = tee_shm_alloc(tee, name_size,
939 TEE_SHM_ALLOC, &shm_name);
940 if (rc)
941 return AVB_IO_RESULT_ERROR_OOM;
942
943 rc = tee_shm_alloc(tee, value_size,
944 TEE_SHM_ALLOC, &shm_buf);
945 if (rc) {
946 rc = AVB_IO_RESULT_ERROR_OOM;
947 goto free_name;
948 }
949
950 memcpy(shm_name->addr, name, name_size);
951 memcpy(shm_buf->addr, value, value_size);
952
953 memset(param, 0, sizeof(param));
954 param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
955 param[0].u.memref.shm = shm_name;
956 param[0].u.memref.size = name_size;
957 param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
958 param[1].u.memref.shm = shm_buf;
959 param[1].u.memref.size = value_size;
960
961 rc = invoke_func(ops->user_data, TA_AVB_CMD_WRITE_PERSIST_VALUE,
962 2, param);
963 if (rc)
964 goto out;
965
966out:
967 tee_shm_free(shm_buf);
968free_name:
969 tee_shm_free(shm_name);
970
971 return rc;
972}
Sam Protsenkof254bd02019-07-31 19:59:08 +0300973#endif
974
Igor Opaniuk7a5fbfe2018-08-10 16:59:59 +0300975/**
Igor Opaniuk3af30e42018-06-03 21:56:38 +0300976 * ============================================================================
977 * AVB2.0 AvbOps alloc/initialisation/free
978 * ============================================================================
979 */
980AvbOps *avb_ops_alloc(int boot_device)
981{
982 struct AvbOpsData *ops_data;
983
984 ops_data = avb_calloc(sizeof(struct AvbOpsData));
985 if (!ops_data)
986 return NULL;
987
988 ops_data->ops.user_data = ops_data;
989
990 ops_data->ops.read_from_partition = read_from_partition;
991 ops_data->ops.write_to_partition = write_to_partition;
992 ops_data->ops.validate_vbmeta_public_key = validate_vbmeta_public_key;
993 ops_data->ops.read_rollback_index = read_rollback_index;
994 ops_data->ops.write_rollback_index = write_rollback_index;
995 ops_data->ops.read_is_device_unlocked = read_is_device_unlocked;
996 ops_data->ops.get_unique_guid_for_partition =
997 get_unique_guid_for_partition;
Igor Opaniukfc1fe012019-04-09 15:38:14 +0200998#ifdef CONFIG_OPTEE_TA_AVB
999 ops_data->ops.write_persistent_value = write_persistent_value;
1000 ops_data->ops.read_persistent_value = read_persistent_value;
1001#endif
Igor Opaniuk7a5fbfe2018-08-10 16:59:59 +03001002 ops_data->ops.get_size_of_partition = get_size_of_partition;
Igor Opaniuk3af30e42018-06-03 21:56:38 +03001003 ops_data->mmc_dev = boot_device;
1004
1005 return &ops_data->ops;
1006}
1007
1008void avb_ops_free(AvbOps *ops)
1009{
1010 struct AvbOpsData *ops_data;
1011
Eugeniu Rosca47e41632018-08-14 02:43:08 +02001012 if (!ops)
Igor Opaniuk3af30e42018-06-03 21:56:38 +03001013 return;
1014
1015 ops_data = ops->user_data;
1016
Jens Wiklander6663e072018-09-25 16:40:20 +02001017 if (ops_data) {
1018#ifdef CONFIG_OPTEE_TA_AVB
1019 if (ops_data->tee)
1020 tee_close_session(ops_data->tee, ops_data->session);
1021#endif
Igor Opaniuk3af30e42018-06-03 21:56:38 +03001022 avb_free(ops_data);
Jens Wiklander6663e072018-09-25 16:40:20 +02001023 }
Igor Opaniuk3af30e42018-06-03 21:56:38 +03001024}