[GUIDE] Computing New CRC and Size Values for Kernel Config Partitions
WD MyCloud Gen1 (Comcerto CPU with Barebox bootloader)
In This Guide
- use factory-supplied GPL Source Code
- use files available in factory firmware (or firmware
sq-*.deb
archives) - analyze leading binary data within
k1m0.env
(partition 7) ork1m1.env
(partition 8) - determine fields to edit
- edit and null-pad kernel config, possibly changing its length
- store new length values to binary area of kernel config files
- compute new
crc32
values- store new crc values to binary area of kernel config files
This guide assumes an advanced use case for your WD MyCloud Gen1.
This is not a beginner guide.
Assumptions
- you have some insight about
barebox
bootloader - you are probably using UART to examine the device as it boots
- you are able to examine WD-Supplied GPL Source Code to confirm the accuracy of the information here
- you know what a kernel configs is
- you know how to write kernel config to the appropriate partition(s)
- you are confident with commandline tools and their usage
- particularly
hexedit
,hexdump
, anddd
- particularly
You may have landed here due to the following errors observed over UART during boot:
sataenv: partition 7 wrong crc on env data
sataenv: partition 8 wrong crc on env data
## editorial comments
## - notice how mac_addr is a bogus value and other values are blank
## - barebox appears to load a placeholder config if no config partitions pass crc
## - you can find the placeholder mac address by searching barebox source code
commandline:$console=ttyS0,115200n8, init=/sbin/init rootfstype=ext4 rw noinitrd mac_addr=00:11:22:33:44:55 model= serial= board_test= btn_status=0
FOREWORD
The source code files referenced in this guide are found inside WD GPL Source Code available on the support downloads page.
A binary representation of a struct named envfs_super
and another struct named envfs_inode
appear at the start of the kernel config partitions (required by bootloader) - thought process follows.
Using the instructions in this guide, I have successfully
- edited the four “size” and “crc” fields described below for an unmodified factory
k1m0.env
- the results are indistinguishable from the original, confirmed using
hexdump
(usage shown below)
- the results are indistinguishable from the original, confirmed using
- edited only the two “crc” fields based on a modified
k1m0.env
(one byte of the content changed, size did not)- the resulting
k1m0.env
passes crc validation during boot on actual hardware
- the resulting
WHERE DOES "wrong crc on env data"
OCCUR?
Using an IDE (or grep), search within barebox-2011.06.0/
subdir inside WD GPL Source Code for the string wrong crc on env data
.
That string appears only in the file barebox-2011.06.0/commands/sataenv.c
. That file shows how a struct of type envfs_super
is populated from the first few bytes of partition 7 (or 8). It also shows how the bootloader validates the magic number and two different crc values found inside envfs_super
, as well as an additional magic number found inside a struct of type envfs_inode
(which itself is loaded from the bytes following envfs_super
).
DECLARATIONS
From the file barebox-2011.06.0/include/envfs.h
- all comments here are original to factory-supplied code (though re-aligned for readability below)
#define ENVFS_MAGIC 0x798fba79 /* some random number */
#define ENVFS_INODE_MAGIC 0x67a8c78d
/*
* Superblock information at the beginning of the FS.
*/
struct envfs_super {
uint32_t magic; /* ENVFS_MAGIC */
uint32_t priority;
uint32_t crc; /* crc for the data */
uint32_t size; /* size of data */
uint32_t flags; /* feature flags */
uint32_t future; /* reserved for future use */
uint32_t sb_crc; /* crc for the superblock */
};
struct envfs_inode {
uint32_t magic; /* ENVFS_INODE_MAGIC */
uint32_t size; /* data size in bytes */
uint32_t namelen; /* The length of the filename _including_ a trailing 0 */
char data[0]; /* The filename (zero terminated) + padding to 4 byte boundary
* followed by the data for this inode.
* The next inode follows after the data + padding to 4 byte
* boundary.
*/
};
EXAMINATION
From examining /usr/share/k1m0.env
(which becomes the contents of partition 7) from factory firmware, it seems clear that k1m0.env
begins with envfs_super
followed immediately by envsfs_inode
(much more follows).
There are exactly 44 bytes of binary data at the beginning of each *.env
file. Assuming sizeof uint32_t
is 32bits (4-bytes), that makes sizeof( envfs_super )
28 bytes (seven fields times four bytes each).
That still leaves 16 bytes .
Using hexdump
with a more complex formatting string
- first, seven 4-byte chunks (28 bytes total), formatted as unsigned int, on their own line
- then, four 4-byte chunks (16 bytes total), formatted as unsigned int, also on their own line
- finally, 524 bytes (522 rounded up to a multiple of 4) as a big character block
- why
522
?- That value appears on the second output line
- It corresponds to the expected position of the field
envfs_inode.size
- It corresponds to the observed size of the actual data (without including the null padding) that follows
envfs_inode
- why
524
?- Source code comments mention null-padding actual data portion to the next 4-byte boundary
- It corresponds to the observed size of the actual data, now including the null padding
- This consumes all the bytes so
hexdump
does not begin repeating the formatting pattern
- also 524 + 16 (16 is the observed size of the likely
envfs_inode
struct) exactly matches540
, which corresponds to observed value inenvfs_super.size
. - the sizes of the actual structs
envfs_super
andenvfs_inode
are both accounted for in the filebarebox-2011.06.0/commands/sataenv.c
- The size field of each struct is also utilized in a manner similar to how we are using it here as far as knowing how much data to read
- why
$> hexdump -e '7/4 "%u\t""\n" 4/4 "%u\t""\n" 524/1 "%c""\n"' k1m0.env
2039462521 0 546255751 540 0 0 4161781478
1739114381 522 1 0
#!/bin/sh
## Button initial state
btn_status=0
get_button_status
sata
satapart 0x3008000 5 0x5000
sata stop
# This is customized for each environment variable script
bootargs="console=ttyS0,115200n8, init=/sbin/init"
bootargs="$bootargs root=/dev/md0 raid=autodetect"
bootargs="$bootargs rootfstype=ext3 rw noinitrd debug initcall_debug swapaccount=1 panic=3"
bootargs="$bootargs mac_addr=$eth0.ethaddr"
bootargs="$bootargs model=$model serial=$serial board_test=$board_test btn_status=$btn_status"
bootm /dev/mem.uImage
hexdump
again, this time only the leading binary chunk (44 bytes) representing what I believe are the structs envfs_super
and envfs_inode
, appearing here (mostly) as unsigned int
- first, one 4-byte chunk as hex, followed by six 4-byte chunks as unsigned int
- still seven chunks total
- then, one 4-byte chunk as hex, followed by three 4-byte chunks as unsigned int
- still four chunks total
- fields are all separated by
tab
character
$> hexdump -n 44 -e '1/4 "x%08x\t" 6/4 "%u\t""\n" 1/4 "x%08x\t" 3/4 "%u\t""\n"' k1m0.env
x798fba79 0 546255751 540 0 0 4161781478
x67a8c78d 522 1 0
You can see the first hex chunk on each line exactly matches the magic number referenced in the comments for the first field of each of the two structs defined the file barebox-2011.06.0/include/envfs.h
.
- The file
barebox-2011.06.0/commands/sataenv.c
does confirm the presence of those magic numbers before proceeding to other tests.
That, and the rest of how barebox-2011.06.0/commands/sataenv.c
appears to work, gives me a good amount of confidence in the following:
-
the first 28 bytes are
envfs_super
-
envfs_super.magic
is first field of struct- observed hex value matches hard-coded hex value
ENVFS_MAGIC
- observed hex value matches hard-coded hex value
-
envfs_super.size
is fourth field of struct- observed value
540
corresponds to observed actual data (524 bytes when rounded up to a multiple of 4-bytes) plus the size ofenvfs_inode
(16 bytes)
- observed value
-
-
the next 16 bytes are
envfs_inode
-
envfs_inode.magic
is first field of struct- observed hex value matches hard-coded hex value for
ENVFS_INODE_MAGIC
- observed hex value matches hard-coded hex value for
-
envfs_inode.size
is second field of struct- Observed value
522
corresponds to observed actual length of data in*.env
file (when rounded up to a multiple of 4-bytes and null-padded)
- Observed value
-
-
it seems
envfs_inode
could hold a filename? However…-
envfs_inode.namelen == 1
(a length which includes the null terminator according to the code comments) -
envfs_inode.data == 0
(which is the null terminator) - the fact
envfs_inode
is 16 bytes on disk is a consequence of it having adata
field (filename) containing an empty string (null terminator only)
-
TWO CHECKSUMS
There are two checksum fields in envfs_super
-
envfs_super.crc
(third field) -
envfs_super.sb_crc
(seventh field) - those fields have very similar names
- I have taken care to use them correctly, below
- If you notice an error, please post a comment and I will edit to fix it
In barebox-2011.06.0/commands/sataenv.c
, it…
- reads
sizeof( envfs_super )
(28 bytes)- well, it reads a whole sector but ultimately looks at only the first 28 bytes
- bailout with error if
"crc32(
first 24 bytes of that data)"
fails to matchenvfs_super.sb_crc
- first 24 bytes is the first six fields of
envfs_super
-
envfs_super.sb_crc
is the seventh field and final four bytes of the 28 bytes read
- first 24 bytes is the first six fields of
-
this means: changing anything in
envfs_super
(like the othercrc
field) requires calculating/writing a newenvfs_super.sb_crc
fromenvfs_super
itself
- reads from disk again, this time based on
envfs_super.size
(540
observed usinghexdump
, above)- bailout with error if
"crc32(
all the data includingenvfs_inode
but notenvfs_super )"
fails to matchenvfs_super.crc
-
envfs_super.crs
is the third field ofenvfs_super
-
-
this means: changing even a single byte in the actual human-readable kernel args requires calculating/writing a new
envfs_super.crc
-
this also means: changing the length of the actual human-readable kernel args requires storing the correct length value in
envfs_inode.size
and"(
that_length+ 16 +
possible_padding)"
inenvfs_super.size
(prior to calculating/writing new CRC values)
- bailout with error if
MY EDUCATED GUESS
Starting at byte zero of your k1m0.env
config file (or config partition)
- the first field of
envfs_super
occupies byte positions 0-3 (zero-based indexing) -
envfs_super.crc
(third field) occupies byte positions 8-11 -
envfs_super.size
(fourth field) occupies byte positions 12-15 -
envfs_super.sb_crc
(seventh field) occupies byte positions 24-27 -
envfs_inode.size
(second field) occupies byte positions 32-34
IF YOU ARE NOT CHANGING THE UNPADDED LENGTH
Are you changing things in a way that the unpadded length within k1m0.env
does not change?
- only a single byte, like
ext3
→ext4
- a single byte in
satapart
directive to load from some other partition number - the
satapart
directive for loading a kernel occupying more than0x5000
512-byte sectors (10MiB)
As long as the unpadded length does not change by even one byte, envfs_inode.size
and envfs_super.size
stay the same. Only the crc values need re-calculating.
- Skip the section immediately below called “MODIFY
k1m0.env
WITH NEW LENGTH VALUES” - Go directly to “MODIFY
k1m0.env
WITH NEW CRC VALUES”
MODIFY k1m0.env
WITH NEW LENGTH VALUES
Make a backup copy of your k1m0.env
before proceeding
Read the actual code and double check my math before proceeding.
!! WARNING !! I have not personally tested changing the length of kernel config files though I used these steps to store factory values (the ones shown below) to the appropriate fields. The resulting file is identical to the original when inspected with hexdump
(as shown earlier in this guide). I believe the procedure described here is accurate.
- edit your config file with a text editor that will not alter/strip the 44 bytes of binary data at the beginning of the document, then save and exit.
- then, edit your config file with
hexedit
(or usehexedit
for all your editing)-
make certain to null-pad the end of the data to the next four-byte boundary
- ie, insert
0
(zero, null)
- ie, insert
- if your last actual char just happens to land at a four-byte boundary
- I am not certain any null bytes are actually required
- the bootloader reads based entirely upon the size it is told to read
- I do not think there is any harm in adding four null bytes, though
- I am not certain any null bytes are actually required
-
make certain to null-pad the end of the data to the next four-byte boundary
-
hexedit
reports your position in hex at the bottom of the screen- remember, zero-based indexing here
- when your cursor is on position 0, it is the first byte
- when your cursor is on position
0x235
(hex), it is byte0x236
th byte -
envfs_super.size
andenvfs_inode.size
are expecting the appropriate number of bytes, not the byte position
Determine and Store envfs_inode.size
- determine your unpadded_end_position
- using
hexedit
, arrow-key to your last actual non-null character - for instance, byte
0x235
(hex) is byte565
(decimal)- use the hex-to-decimal functionality of a calculator
- using
- determine new value for
envfs_inode.size
-
(
unpadded_end_position_in_decimal)+ 1 - 44 )
- ie
( 565 + 1 - 44 ) = 522
- 44 is total length of the binary section at beginning of file
- 44 is
sizeof(envfs_super) + sizeof(envfs_inode)
-
-
Write New
envfs_inode.size
(using one-liner, below)- this is the second field of
envfs_inode
, beginning at byte position 32 ink1m0.env
-
REPLACE
522
INprintf
WITH YOUR NEW VALUE FORenvfs_inode.size
(determined immediately above)
## use `printf` to produce ascii hex string from the value `522`, padded to eight hex digits ## pipe that ascii to `xxd` to "revert" ascii hex representation to binary (`-plain` with `-revert` means the *input* lacks hexdump-stype line numbers) ## pipe that binary to `xxd` again to swap the byte order (only works on binary input and only produces **ASCII output with hexdump-style line numbers**) ## pipe that ascii to `xxd` one final time to "revert" to binary again (byte-swapped this time) (xxd knows to ignore hexdump-style line numbers) ## pipe that byte-swapped binary value to `dd` (`if=<stdin>` when not specified) ## using four-byte blocks because `bs=4` ## seek past *eight* four-byte blocks (`seek=8`) ## write *one* four-byte block starting **byte position 32** (`count=1`) ## DO NOT TRUNCATE THE THE FILE $> printf "%08x" 522 | xxd -revert -plain | xxd -e -g4 | xxd -revert | dd of=k1m0.env bs=4 count=1 seek=8 conv=notrunc
- this is the second field of
Determine and Store envfs_super.size
- determine your padded_end_position
- using
hexedit
, arrow-key to your final null byte - for instance, byte
0x237
(hex) is byte567
(decimal)
- using
- determine the new value for
envfs_super.size
-
(
padded_end_position_in_decimal)+ 1 - 28 )
- ie
( 567 + 1 - 28 ) = 540
- 28 is
sizeof(envfs_super)
- 28 is
-
-
Write New
envfs_super.size
(using one-liner, below)- this is the fourth field of
envfs_super
, beginning at byte position 12 ink1m0.env
-
REPLACE
540
INprintf
WITH YOUR NEW VALUE FORenvfs_super.size
(determined immediately above)
## use `printf` to produce ascii hex string from the value `540`, padded to eight hex digits ## pipe that ascii to `xxd` to "revert" ascii hex representation to binary (`-plain` with `-revert` means the *input* lacks hexdump-stype line numbers) ## pipe that binary to `xxd` again to swap the byte order (only works on binary input and only produces **ASCII output with hexdump-style line numbers**) ## pipe that ascii to `xxd` one final time to "revert" to binary again (byte-swapped this time) (xxd knows to ignore hexdump-style line numbers) ## pipe that byte-swapped binary value to `dd` (`if=<stdin>` when not specified) ## using four-byte blocks because `bs=4` ## seek past *three* four-byte blocks (`seek=3`) ## write *one* four-byte block starting at **byte position 12** (`count=1`) ## DO NOT TRUNCATE THE THE FILE $> printf "%08x" 540 | xxd -revert -plain | xxd -e -g4 | xxd -revert | dd of=k1m0.env bs=4 count=1 seek=3 conv=notrunc
- this is the fourth field of
YOU STILL MUST COMPUTE CRC VALUES
- length values are part of the data used in computing crc values
- you must set both of the new length values prior to performing the steps immediately below in “Modify
k1m0.env
with New CRC Values”
MODIFY k1m0.env
WITH NEW CRC VALUES
Make a backup copy of your k1m0.env
before proceeding
This section uses (obviously named) temporary files to compute CRC
crc32
reads only from a file (at least mine does)
Delete temporary files when you are finished
Calculate and Store envfs_super.crc
- this is the third field of
envfs_super
, beginning at byte position 8 ink1m0.env
- Extract fragment from
k1m0.env
- read
envfs_super.size
bytes starting at byte 28 -
byte 28 is where
envfs_inode
begins
## replace `count=540` with your `envfs_super.size` value ## read `540` one-byte blocks, skipping the first `28` one-byte blocks $> dd if=k1m0.env bs=1 skip=28 count=540 of=temp-envfs_super.crc
- temp file now contains fragment of
k1m0.env
needed to compute new value forenvfs_super.crc
- read
-
Calculate CRC and Store CRC (using one-liner, below)
** OR **## calculate checksum of the fragment within temp file (produces ascii hex string) ## pipe that ascii to `xxd` to "revert" ascii hex representation to binary (`-plain` with `-revert` means the *input* lacks hexdump-stype line numbers) ## pipe that binary to `xxd` again to swap the byte order (only works on binary input and only produces **ASCII output with hexdump-style line numbers**) ## pipe that ascii to `xxd` one final time to "revert" to binary again (byte-swapped this time) (xxd knows to ignore hexdump-style line numbers) ## pipe that byte-swapped binary value to `dd` (`if=<stdin>` when not specified) ## using four-byte blocks because `bs=4` ## seek past *two* four-byte blocks (`seek=2`) ## write *one* four-byte block starting at **byte position 8** (`count=1`) ## DO NOT TRUNCATE THE THE FILE $> crc32 temp-envfs_super.crc | xxd -revert -plain | xxd -e -g4 | xxd -revert | dd of=k1m0.env bs=4 count=1 seek=2 conv=notrunc
You could take the hex value provided by$> crc32 temp-envfs_super.crc
and
usehexedit
to manually swap bytes and enter it in the proper place
Calculate and Store envfs_super.sb_crc
- this is the seventh field of
envfs_super
, beginning at byte position 24 ink1m0.env
- Extract fragment from
k1m0.env
- read 24 bytes starting at byte 0
- this is the first six fields of
envfs_super
- the seventh field of
envfs_super
is not used when bootloader confirms CRC - it should not be used when generating CRC (plus, you are about to change field seven anyway)
## read 6 four-byte blocks starting at the beginning of `k1m0.env` ## this temp file does **NOT** depend on any size value you previously computed $> dd if=k1m0.env bs=4 count=6 of=temp-envfs_super.sb_crc
- temp file now contains fragment of
k1m0.env
needed to compute new value forenvfs_super.sb_crc
-
Calculate SB_CRC and Store SB_CRC (using one-liner, below)
## calculate checksum of the fragment within temp file (produces ascii hex string) ## pipe that ascii to `xxd` to "revert" ascii hex representation to binary (`-plain` with `-revert` means the *input* lacks hexdump-stype line numbers) ## pipe that binary to `xxd` again to swap the byte order (only works on binary input and only produces **ASCII output with hexdump-style line numbers**) ## pipe that ascii to `xxd` one final time to "revert" to binary again (byte-swapped this time) (xxd knows to ignore hexdump-style line numbers) ## pipe that byte-swapped binary value to `dd` (`if=<stdin>` when not specified) ## using four-byte blocks because `bs=4` ## seek past *six* four-byte blocks (`seek=6`) ## write *one* four-byte block starting at **byte position 24** (`count=1) ## DO NOT TRUNCATE THE THE FILE $> crc32 temp-envfs_super.sb_crc | xxd -revert -plain | xxd -e -g4 | xxd -revert | dd of=k1m0.env bs=4 count=1 seek=6 conv=notrunc
!! DONE !!
Confirm your k1m0.env
looks correct by using hexdump
as shown much earlier
edits: typos, formatting, clarification