Commit fc1fe01b08cedd77a194bb82fa81af4fe1e39031

Authored by Igor Opaniuk
Committed by Tom Rini
1 parent b85d155199

avb: add support for named persistent values

AVB 2.0 spec. revision 1.1 introduces support for named persistent values
that must be tamper evident and allows AVB to store arbitrary key-value
pairs [1].

Introduce implementation of two additional AVB operations
read_persistent_value()/write_persistent_value() for retrieving/storing
named persistent values.

Correspondent pull request in the OP-TEE OS project repo [2].

[1]: https://android.googlesource.com/platform/external/avb/+/android-9.0.0_r22
[2]: https://github.com/OP-TEE/optee_os/pull/2699

Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Sam Protsenko <semen.protsenko@linaro.org>
Signed-off-by: Igor Opaniuk <igor.opaniuk@gmail.com>

Showing 7 changed files with 351 additions and 22 deletions Side-by-side Diff

... ... @@ -340,6 +340,76 @@
340 340 return CMD_RET_FAILURE;
341 341 }
342 342  
  343 +int do_avb_read_pvalue(cmd_tbl_t *cmdtp, int flag, int argc,
  344 + char * const argv[])
  345 +{
  346 + const char *name;
  347 + size_t bytes;
  348 + size_t bytes_read;
  349 + void *buffer;
  350 + char *endp;
  351 +
  352 + if (!avb_ops) {
  353 + printf("AVB 2.0 is not initialized, run 'avb init' first\n");
  354 + return CMD_RET_FAILURE;
  355 + }
  356 +
  357 + if (argc != 3)
  358 + return CMD_RET_USAGE;
  359 +
  360 + name = argv[1];
  361 + bytes = simple_strtoul(argv[2], &endp, 10);
  362 + if (*endp && *endp != '\n')
  363 + return CMD_RET_USAGE;
  364 +
  365 + buffer = malloc(bytes);
  366 + if (!buffer)
  367 + return CMD_RET_FAILURE;
  368 +
  369 + if (avb_ops->read_persistent_value(avb_ops, name, bytes, buffer,
  370 + &bytes_read) == AVB_IO_RESULT_OK) {
  371 + printf("Read %ld bytes, value = %s\n", bytes_read,
  372 + (char *)buffer);
  373 + free(buffer);
  374 + return CMD_RET_SUCCESS;
  375 + }
  376 +
  377 + printf("Failed to read persistent value\n");
  378 +
  379 + free(buffer);
  380 +
  381 + return CMD_RET_FAILURE;
  382 +}
  383 +
  384 +int do_avb_write_pvalue(cmd_tbl_t *cmdtp, int flag, int argc,
  385 + char * const argv[])
  386 +{
  387 + const char *name;
  388 + const char *value;
  389 +
  390 + if (!avb_ops) {
  391 + printf("AVB 2.0 is not initialized, run 'avb init' first\n");
  392 + return CMD_RET_FAILURE;
  393 + }
  394 +
  395 + if (argc != 3)
  396 + return CMD_RET_USAGE;
  397 +
  398 + name = argv[1];
  399 + value = argv[2];
  400 +
  401 + if (avb_ops->write_persistent_value(avb_ops, name, strlen(value) + 1,
  402 + (const uint8_t *)value) ==
  403 + AVB_IO_RESULT_OK) {
  404 + printf("Wrote %ld bytes\n", strlen(value) + 1);
  405 + return CMD_RET_SUCCESS;
  406 + }
  407 +
  408 + printf("Failed to write persistent value\n");
  409 +
  410 + return CMD_RET_FAILURE;
  411 +}
  412 +
343 413 static cmd_tbl_t cmd_avb[] = {
344 414 U_BOOT_CMD_MKENT(init, 2, 0, do_avb_init, "", ""),
345 415 U_BOOT_CMD_MKENT(read_rb, 2, 0, do_avb_read_rb, "", ""),
... ... @@ -350,6 +420,10 @@
350 420 U_BOOT_CMD_MKENT(read_part_hex, 4, 0, do_avb_read_part_hex, "", ""),
351 421 U_BOOT_CMD_MKENT(write_part, 5, 0, do_avb_write_part, "", ""),
352 422 U_BOOT_CMD_MKENT(verify, 1, 0, do_avb_verify_part, "", ""),
  423 +#ifdef CONFIG_OPTEE_TA_AVB
  424 + U_BOOT_CMD_MKENT(read_pvalue, 3, 0, do_avb_read_pvalue, "", ""),
  425 + U_BOOT_CMD_MKENT(write_pvalue, 3, 0, do_avb_write_pvalue, "", ""),
  426 +#endif
353 427 };
354 428  
355 429 static int do_avb(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
... ... @@ -384,6 +458,10 @@
384 458 " partition <partname> and print to stdout\n"
385 459 "avb write_part <partname> <offset> <num> <addr> - write <num> bytes to\n"
386 460 " <partname> by <offset> using data from <addr>\n"
  461 +#ifdef CONFIG_OPTEE_TA_AVB
  462 + "avb read_pvalue <name> <bytes> - read a persistent value <name>\n"
  463 + "avb write_pvalue <name> <value> - write a persistent value <name>\n"
  464 +#endif
387 465 "avb verify - run verification process using hash data\n"
388 466 " from vbmeta structure\n"
389 467 );
... ... @@ -647,6 +647,10 @@
647 647 return AVB_IO_RESULT_OK;
648 648 case TEE_ERROR_OUT_OF_MEMORY:
649 649 return AVB_IO_RESULT_ERROR_OOM;
  650 + case TEE_ERROR_STORAGE_NO_SPACE:
  651 + return AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE;
  652 + case TEE_ERROR_ITEM_NOT_FOUND:
  653 + return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
650 654 case TEE_ERROR_TARGET_DEAD:
651 655 /*
652 656 * The TA has paniced, close the session to reload the TA
... ... @@ -847,6 +851,123 @@
847 851 return AVB_IO_RESULT_OK;
848 852 }
849 853  
  854 +static AvbIOResult read_persistent_value(AvbOps *ops,
  855 + const char *name,
  856 + size_t buffer_size,
  857 + u8 *out_buffer,
  858 + size_t *out_num_bytes_read)
  859 +{
  860 + AvbIOResult rc;
  861 + struct tee_shm *shm_name;
  862 + struct tee_shm *shm_buf;
  863 + struct tee_param param[2];
  864 + struct udevice *tee;
  865 + size_t name_size = strlen(name) + 1;
  866 +
  867 + if (get_open_session(ops->user_data))
  868 + return AVB_IO_RESULT_ERROR_IO;
  869 +
  870 + tee = ((struct AvbOpsData *)ops->user_data)->tee;
  871 +
  872 + rc = tee_shm_alloc(tee, name_size,
  873 + TEE_SHM_ALLOC, &shm_name);
  874 + if (rc)
  875 + return AVB_IO_RESULT_ERROR_OOM;
  876 +
  877 + rc = tee_shm_alloc(tee, buffer_size,
  878 + TEE_SHM_ALLOC, &shm_buf);
  879 + if (rc) {
  880 + rc = AVB_IO_RESULT_ERROR_OOM;
  881 + goto free_name;
  882 + }
  883 +
  884 + memcpy(shm_name->addr, name, name_size);
  885 +
  886 + memset(param, 0, sizeof(param));
  887 + param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
  888 + param[0].u.memref.shm = shm_name;
  889 + param[0].u.memref.size = name_size;
  890 + param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INOUT;
  891 + param[1].u.memref.shm = shm_buf;
  892 + param[1].u.memref.size = buffer_size;
  893 +
  894 + rc = invoke_func(ops->user_data, TA_AVB_CMD_READ_PERSIST_VALUE,
  895 + 2, param);
  896 + if (rc)
  897 + goto out;
  898 +
  899 + if (param[1].u.memref.size > buffer_size) {
  900 + rc = AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
  901 + goto out;
  902 + }
  903 +
  904 + *out_num_bytes_read = param[1].u.memref.size;
  905 +
  906 + memcpy(out_buffer, shm_buf->addr, *out_num_bytes_read);
  907 +
  908 +out:
  909 + tee_shm_free(shm_buf);
  910 +free_name:
  911 + tee_shm_free(shm_name);
  912 +
  913 + return rc;
  914 +}
  915 +
  916 +static AvbIOResult write_persistent_value(AvbOps *ops,
  917 + const char *name,
  918 + size_t value_size,
  919 + const u8 *value)
  920 +{
  921 + AvbIOResult rc;
  922 + struct tee_shm *shm_name;
  923 + struct tee_shm *shm_buf;
  924 + struct tee_param param[2];
  925 + struct udevice *tee;
  926 + size_t name_size = strlen(name) + 1;
  927 +
  928 + if (get_open_session(ops->user_data))
  929 + return AVB_IO_RESULT_ERROR_IO;
  930 +
  931 + tee = ((struct AvbOpsData *)ops->user_data)->tee;
  932 +
  933 + if (!value_size)
  934 + return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
  935 +
  936 + rc = tee_shm_alloc(tee, name_size,
  937 + TEE_SHM_ALLOC, &shm_name);
  938 + if (rc)
  939 + return AVB_IO_RESULT_ERROR_OOM;
  940 +
  941 + rc = tee_shm_alloc(tee, value_size,
  942 + TEE_SHM_ALLOC, &shm_buf);
  943 + if (rc) {
  944 + rc = AVB_IO_RESULT_ERROR_OOM;
  945 + goto free_name;
  946 + }
  947 +
  948 + memcpy(shm_name->addr, name, name_size);
  949 + memcpy(shm_buf->addr, value, value_size);
  950 +
  951 + memset(param, 0, sizeof(param));
  952 + param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
  953 + param[0].u.memref.shm = shm_name;
  954 + param[0].u.memref.size = name_size;
  955 + param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT;
  956 + param[1].u.memref.shm = shm_buf;
  957 + param[1].u.memref.size = value_size;
  958 +
  959 + rc = invoke_func(ops->user_data, TA_AVB_CMD_WRITE_PERSIST_VALUE,
  960 + 2, param);
  961 + if (rc)
  962 + goto out;
  963 +
  964 +out:
  965 + tee_shm_free(shm_buf);
  966 +free_name:
  967 + tee_shm_free(shm_name);
  968 +
  969 + return rc;
  970 +}
850 971 /**
851 972 * ============================================================================
852 973 * AVB2.0 AvbOps alloc/initialisation/free
... ... @@ -870,6 +991,10 @@
870 991 ops_data->ops.read_is_device_unlocked = read_is_device_unlocked;
871 992 ops_data->ops.get_unique_guid_for_partition =
872 993 get_unique_guid_for_partition;
  994 +#ifdef CONFIG_OPTEE_TA_AVB
  995 + ops_data->ops.write_persistent_value = write_persistent_value;
  996 + ops_data->ops.read_persistent_value = read_persistent_value;
  997 +#endif
873 998 ops_data->ops.get_size_of_partition = get_size_of_partition;
874 999 ops_data->mmc_dev = boot_device;
875 1000  
drivers/tee/sandbox.c
... ... @@ -14,6 +14,7 @@
14 14 * available.
15 15 */
16 16  
  17 +static const u32 pstorage_max = 16;
17 18 /**
18 19 * struct ta_entry - TA entries
19 20 * @uuid: UUID of an emulated TA
... ... @@ -24,8 +25,11 @@
24 25 */
25 26 struct ta_entry {
26 27 struct tee_optee_ta_uuid uuid;
27   - u32 (*open_session)(uint num_params, struct tee_param *params);
28   - u32 (*invoke_func)(u32 func, uint num_params, struct tee_param *params);
  28 + u32 (*open_session)(struct udevice *dev, uint num_params,
  29 + struct tee_param *params);
  30 + u32 (*invoke_func)(struct udevice *dev,
  31 + u32 func, uint num_params,
  32 + struct tee_param *params);
29 33 };
30 34  
31 35 #ifdef CONFIG_OPTEE_TA_AVB
... ... @@ -59,10 +63,8 @@
59 63 return TEE_ERROR_BAD_PARAMETERS;
60 64 }
61 65  
62   -static u64 ta_avb_rollback_indexes[TA_AVB_MAX_ROLLBACK_LOCATIONS];
63   -static u32 ta_avb_lock_state;
64   -
65   -static u32 ta_avb_open_session(uint num_params, struct tee_param *params)
  66 +static u32 ta_avb_open_session(struct udevice *dev, uint num_params,
  67 + struct tee_param *params)
66 68 {
67 69 /*
68 70 * We don't expect additional parameters when opening a session to
69 71  
70 72  
... ... @@ -73,12 +75,17 @@
73 75 num_params, params);
74 76 }
75 77  
76   -static u32 ta_avb_invoke_func(u32 func, uint num_params,
  78 +static u32 ta_avb_invoke_func(struct udevice *dev, u32 func, uint num_params,
77 79 struct tee_param *params)
78 80 {
  81 + struct sandbox_tee_state *state = dev_get_priv(dev);
  82 + ENTRY e, *ep;
  83 + char *name;
79 84 u32 res;
80 85 uint slot;
81 86 u64 val;
  87 + char *value;
  88 + u32 value_sz;
82 89  
83 90 switch (func) {
84 91 case TA_AVB_CMD_READ_ROLLBACK_INDEX:
85 92  
... ... @@ -91,12 +98,12 @@
91 98 return res;
92 99  
93 100 slot = params[0].u.value.a;
94   - if (slot >= ARRAY_SIZE(ta_avb_rollback_indexes)) {
  101 + if (slot >= ARRAY_SIZE(state->ta_avb_rollback_indexes)) {
95 102 printf("Rollback index slot out of bounds %u\n", slot);
96 103 return TEE_ERROR_BAD_PARAMETERS;
97 104 }
98 105  
99   - val = ta_avb_rollback_indexes[slot];
  106 + val = state->ta_avb_rollback_indexes[slot];
100 107 params[1].u.value.a = val >> 32;
101 108 params[1].u.value.b = val;
102 109 return TEE_SUCCESS;
103 110  
104 111  
... ... @@ -111,16 +118,16 @@
111 118 return res;
112 119  
113 120 slot = params[0].u.value.a;
114   - if (slot >= ARRAY_SIZE(ta_avb_rollback_indexes)) {
  121 + if (slot >= ARRAY_SIZE(state->ta_avb_rollback_indexes)) {
115 122 printf("Rollback index slot out of bounds %u\n", slot);
116 123 return TEE_ERROR_BAD_PARAMETERS;
117 124 }
118 125  
119 126 val = (u64)params[1].u.value.a << 32 | params[1].u.value.b;
120   - if (val < ta_avb_rollback_indexes[slot])
  127 + if (val < state->ta_avb_rollback_indexes[slot])
121 128 return TEE_ERROR_SECURITY;
122 129  
123   - ta_avb_rollback_indexes[slot] = val;
  130 + state->ta_avb_rollback_indexes[slot] = val;
124 131 return TEE_SUCCESS;
125 132  
126 133 case TA_AVB_CMD_READ_LOCK_STATE:
... ... @@ -132,7 +139,7 @@
132 139 if (res)
133 140 return res;
134 141  
135   - params[0].u.value.a = ta_avb_lock_state;
  142 + params[0].u.value.a = state->ta_avb_lock_state;
136 143 return TEE_SUCCESS;
137 144  
138 145 case TA_AVB_CMD_WRITE_LOCK_STATE:
139 146  
140 147  
... ... @@ -144,14 +151,65 @@
144 151 if (res)
145 152 return res;
146 153  
147   - if (ta_avb_lock_state != params[0].u.value.a) {
148   - ta_avb_lock_state = params[0].u.value.a;
149   - memset(ta_avb_rollback_indexes, 0,
150   - sizeof(ta_avb_rollback_indexes));
  154 + if (state->ta_avb_lock_state != params[0].u.value.a) {
  155 + state->ta_avb_lock_state = params[0].u.value.a;
  156 + memset(state->ta_avb_rollback_indexes, 0,
  157 + sizeof(state->ta_avb_rollback_indexes));
151 158 }
152 159  
153 160 return TEE_SUCCESS;
  161 + case TA_AVB_CMD_READ_PERSIST_VALUE:
  162 + res = check_params(TEE_PARAM_ATTR_TYPE_MEMREF_INPUT,
  163 + TEE_PARAM_ATTR_TYPE_MEMREF_INOUT,
  164 + TEE_PARAM_ATTR_TYPE_NONE,
  165 + TEE_PARAM_ATTR_TYPE_NONE,
  166 + num_params, params);
  167 + if (res)
  168 + return res;
154 169  
  170 + name = params[0].u.memref.shm->addr;
  171 +
  172 + value = params[1].u.memref.shm->addr;
  173 + value_sz = params[1].u.memref.size;
  174 +
  175 + e.key = name;
  176 + e.data = NULL;
  177 + hsearch_r(e, FIND, &ep, &state->pstorage_htab, 0);
  178 + if (!ep)
  179 + return TEE_ERROR_ITEM_NOT_FOUND;
  180 +
  181 + value_sz = strlen(ep->data);
  182 + memcpy(value, ep->data, value_sz);
  183 +
  184 + return TEE_SUCCESS;
  185 + case TA_AVB_CMD_WRITE_PERSIST_VALUE:
  186 + res = check_params(TEE_PARAM_ATTR_TYPE_MEMREF_INPUT,
  187 + TEE_PARAM_ATTR_TYPE_MEMREF_INPUT,
  188 + TEE_PARAM_ATTR_TYPE_NONE,
  189 + TEE_PARAM_ATTR_TYPE_NONE,
  190 + num_params, params);
  191 + if (res)
  192 + return res;
  193 +
  194 + name = params[0].u.memref.shm->addr;
  195 +
  196 + value = params[1].u.memref.shm->addr;
  197 + value_sz = params[1].u.memref.size;
  198 +
  199 + e.key = name;
  200 + e.data = NULL;
  201 + hsearch_r(e, FIND, &ep, &state->pstorage_htab, 0);
  202 + if (ep)
  203 + hdelete_r(e.key, &state->pstorage_htab, 0);
  204 +
  205 + e.key = name;
  206 + e.data = value;
  207 + hsearch_r(e, ENTER, &ep, &state->pstorage_htab, 0);
  208 + if (!ep)
  209 + return TEE_ERROR_OUT_OF_MEMORY;
  210 +
  211 + return TEE_SUCCESS;
  212 +
155 213 default:
156 214 return TEE_ERROR_NOT_SUPPORTED;
157 215 }
... ... @@ -225,7 +283,7 @@
225 283 return 0;
226 284 }
227 285  
228   - arg->ret = ta->open_session(num_params, params);
  286 + arg->ret = ta->open_session(dev, num_params, params);
229 287 arg->ret_origin = TEE_ORIGIN_TRUSTED_APP;
230 288  
231 289 if (!arg->ret) {
... ... @@ -261,7 +319,7 @@
261 319 return -EINVAL;
262 320 }
263 321  
264   - arg->ret = ta->invoke_func(arg->func, num_params, params);
  322 + arg->ret = ta->invoke_func(dev, arg->func, num_params, params);
265 323 arg->ret_origin = TEE_ORIGIN_TRUSTED_APP;
266 324  
267 325 return 0;
... ... @@ -285,6 +343,29 @@
285 343 return 0;
286 344 }
287 345  
  346 +static int sandbox_tee_remove(struct udevice *dev)
  347 +{
  348 + struct sandbox_tee_state *state = dev_get_priv(dev);
  349 +
  350 + hdestroy_r(&state->pstorage_htab);
  351 +
  352 + return 0;
  353 +}
  354 +
  355 +static int sandbox_tee_probe(struct udevice *dev)
  356 +{
  357 + struct sandbox_tee_state *state = dev_get_priv(dev);
  358 + /*
  359 + * With this hastable we emulate persistent storage,
  360 + * which should contain persistent values
  361 + * between different sessions/command invocations.
  362 + */
  363 + if (!hcreate_r(pstorage_max, &state->pstorage_htab))
  364 + return TEE_ERROR_OUT_OF_MEMORY;
  365 +
  366 + return 0;
  367 +}
  368 +
288 369 static const struct tee_driver_ops sandbox_tee_ops = {
289 370 .get_version = sandbox_tee_get_version,
290 371 .open_session = sandbox_tee_open_session,
... ... @@ -305,5 +386,7 @@
305 386 .of_match = sandbox_tee_match,
306 387 .ops = &sandbox_tee_ops,
307 388 .priv_auto_alloc_size = sizeof(struct sandbox_tee_state),
  389 + .probe = sandbox_tee_probe,
  390 + .remove = sandbox_tee_remove,
308 391 };
include/sandboxtee.h
... ... @@ -6,16 +6,25 @@
6 6 #ifndef __SANDBOXTEE_H
7 7 #define __SANDBOXTEE_H
8 8  
  9 +#include <search.h>
  10 +#include <tee/optee_ta_avb.h>
  11 +
9 12 /**
10 13 * struct sandbox_tee_state - internal state of the sandbox TEE
11   - * @session: current open session
12   - * @num_shms: number of registered shared memory objects
13   - * @ta: Trusted Application of current session
  14 + * @session: current open session
  15 + * @num_shms: number of registered shared memory objects
  16 + * @ta: Trusted Application of current session
  17 + * @ta_avb_rollback_indexes TA avb rollback indexes storage
  18 + * @ta_avb_lock_state TA avb lock state storage
  19 + * @pstorage_htab named persistent values storage
14 20 */
15 21 struct sandbox_tee_state {
16 22 u32 session;
17 23 int num_shms;
18 24 void *ta;
  25 + u64 ta_avb_rollback_indexes[TA_AVB_MAX_ROLLBACK_LOCATIONS];
  26 + u32 ta_avb_lock_state;
  27 + struct hsearch_data pstorage_htab;
19 28 };
20 29  
21 30 #endif /*__SANDBOXTEE_H*/
... ... @@ -43,7 +43,9 @@
43 43 #define TEE_ERROR_COMMUNICATION 0xffff000e
44 44 #define TEE_ERROR_SECURITY 0xffff000f
45 45 #define TEE_ERROR_OUT_OF_MEMORY 0xffff000c
  46 +#define TEE_ERROR_OVERFLOW 0xffff300f
46 47 #define TEE_ERROR_TARGET_DEAD 0xffff3024
  48 +#define TEE_ERROR_STORAGE_NO_SPACE 0xffff3041
47 49  
48 50 #define TEE_ORIGIN_COMMS 0x00000002
49 51 #define TEE_ORIGIN_TEE 0x00000003
include/tee/optee_ta_avb.h
... ... @@ -45,5 +45,21 @@
45 45 */
46 46 #define TA_AVB_CMD_WRITE_LOCK_STATE 3
47 47  
  48 +/*
  49 + * Reads a persistent value corresponding to the given name.
  50 + *
  51 + * in params[0].u.memref: persistent value name
  52 + * out params[1].u.memref: read persistent value buffer
  53 + */
  54 +#define TA_AVB_CMD_READ_PERSIST_VALUE 4
  55 +
  56 +/*
  57 + * Writes a persistent value corresponding to the given name.
  58 + *
  59 + * in params[0].u.memref: persistent value name
  60 + * in params[1].u.memref: persistent value buffer to write
  61 + */
  62 +#define TA_AVB_CMD_WRITE_PERSIST_VALUE 5
  63 +
48 64 #endif /* __TA_AVB_H */
test/py/tests/test_avb.py
... ... @@ -116,4 +116,20 @@
116 116 response = u_boot_console.run_command('cmp 0x%x 0x%x 40' %
117 117 (temp_addr, temp_addr2))
118 118 assert response.find('64 word')
  119 +
  120 +
  121 +@pytest.mark.buildconfigspec('cmd_avb')
  122 +@pytest.mark.buildconfigspec('optee_ta_avb')
  123 +def test_avb_persistent_values(u_boot_console):
  124 + """Test reading/writing persistent storage to avb
  125 + """
  126 +
  127 + response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
  128 + assert response == ''
  129 +
  130 + response = u_boot_console.run_command('avb write_pvalue test value_value')
  131 + assert response == 'Wrote 12 bytes'
  132 +
  133 + response = u_boot_console.run_command('avb read_pvalue test 12')
  134 + assert response == 'Read 12 bytes, value = value_value'