diff --git a/faq/how_to_seal.rst b/faq/how_to_seal.rst index b07c11d..69da6b4 100644 --- a/faq/how_to_seal.rst +++ b/faq/how_to_seal.rst @@ -30,46 +30,215 @@ sealing capability (the same one that we used to perform the seal). +.. code-block:: c + :emphasize-lines: 39,43 + + // seal.c + #include + #include + #include + #include + #include + + char * get_secret() { + char * buf = (char *)malloc(32); + memset(buf, 0, 32); + strcpy(buf, "Shh! This is a secret!"); + return buf; + } + + void * get_system_sealer() { + void * sealcap; + size_t sealcap_size = sizeof(sealcap); + if (sysctlbyname("security.cheri.sealcap", &sealcap, &sealcap_size, NULL, 0) < 0) + { + fprintf(stderr, "Fatal error. Cannot get `security.cheri.sealcap`."); + exit(1); + } + return sealcap; + } + + void untrusted_3rd_party_func(void * data) { + // this function will only print metadata of the sealed capability + // and that's safe + printf("[untrusted func] sealed: %#p, valid: %d\n", data, cheri_is_valid(data)); + } + + int main() { + char * buf = get_secret(); + printf("[main] buf: %#p, valid: %d\n", buf, cheri_is_valid(buf)); + + // we can seal the capability using `cheri_seal` + void * sealer = get_system_sealer(); + printf("[main] sealer: %#p, valid: %d\n", sealer, cheri_is_valid(sealer)); + void * sealed = cheri_seal(buf, sealer); + printf("[main] sealed: %#p, valid: %d\n", sealed, cheri_is_valid(sealed)); + + // we can unseal the capability with the original sealer + char * unsealed = (char *)cheri_unseal(sealed, sealer); + printf("[after] unsealed: %#p, %s\n", unsealed, unsealed); + + // pass the sealed capability to an untrusted function + untrusted_3rd_party_func(sealed); + } + + +Note that the example code above won't be able to compile with the MUSL libc library because +MUSL libc does not have support for sysctl. Therefore, we have to either compile it natively +on CheriBSD or with the GCC toolchain. + +If we build and run the program, we will see the following output: + +.. code-block:: bash + + # compile and run on a Morello system + $ clang-morello -march=morello+c64 -mabi=purecap \ + -Xclang -morello-vararg=new \ + -O0 -g seal.c -o seal + $ ./seal + [main] buf: 0x40838000 [rwRW,0x40838000-0x40838020], valid: 1 + [main] sealer: 0x4 [,0x4-0x2000], valid: 1 + [main] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + [after] unsealed: 0x40838000 [rwRW,0x40838000-0x40838020], Shh! This is a secret! + [untrusted func] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + + +Now if we try read the sealed capability in the untrusted function, we'll receive a +`SIGPROT` fault: + +.. code-block:: c + :emphasize-lines: 3 + + void untrusted_3rd_party_func(void * data) { + printf("[untrusted func] sealed: %#p, valid: %d\n", data, cheri_is_valid(data)); + printf("[untrusted func] read as char *: %s\n", (char *)data); + } + + +.. code-block:: bash + :emphasize-lines: 7 + + $ ./seal + [main] buf: 0x40838000 [rwRW,0x40838000-0x40838020], valid: 1 + [main] sealer: 0x4 [,0x4-0x2000], valid: 1 + [main] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + [after] unsealed: 0x40838000 [rwRW,0x40838000-0x40838020], Shh! This is a secret! + [untrusted func] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + In-address space security exception (core dumped) + + +However, since we were using the OS canonical in the above example, which is +accessible by everyone, the attacker can also get a copy of that and try to +unseal the capabilities we pass to these untrusted functions. For example: + +.. code-block:: c + :emphasize-lines: 6-7 + + void untrusted_3rd_party_func(void * data) { + printf("[untrusted func] sealed: %#p, valid: %d\n", data, cheri_is_valid(data)); + + void * root_sealer; + size_t sealcap_size = sizeof(root_sealer); + sysctlbyname("security.cheri.sealcap", &root_sealer, &sealcap_size, NULL, 0); + data = cheri_unseal(data, root_sealer); + printf("[untrusted func] read as char *: %s\n", (char *)data); + } + + +.. code-block:: bash + :emphasize-lines: 7 + + $ ./seal + [main] buf: 0x40838000 [rwRW,0x40838000-0x40838020], valid: 1 + [main] sealer: 0x4 [,0x4-0x2000], valid: 1 + [main] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + [after] unsealed: 0x40838000 [rwRW,0x40838000-0x40838020], Shh! This is a secret! + [untrusted func] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + [untrusted func] read as char *: Shh! This is a secret! + + +To address this potential security issue, we can create our own sealer, and seal sensetive +capabilities with it instead of the OS canonical one. The reason for doing so is that, in +CHERI, a sealed capability can only be unseal with its original sealer. + +In order to create our own sealer, we can derive it from the the OS canonical one. But before +that, we can take a look at output for the OS canonical one, which serves as the userspace +root sealer. + +.. code-block:: bash + + $ ./seal + ... + [main] sealer: 0x4 [,0x4-0x2000], valid: 1 + ... -.. code-block:: C - :emphasize-lines: 28,32 - #include - #include - #include - #include - #include - #include - - - int main() - { - void *sealcap; - size_t sealcap_size = sizeof(sealcap); - if (sysctlbyname("security.cheri.sealcap", &sealcap, &sealcap_size, NULL, 0) < 0) - { - printf("Fatal error. Cannot get `security.cheri.sealcap`."); - exit(1); - } - - printf("---- sealcap ----\n"); - printf("sealcap: %#p\n", sealcap); - - void *buffer = malloc(64); - printf("---- buffer (before sealing) ----\n"); - printf("buffer: %#p\n", buffer); - - buffer = cheri_seal(buffer, sealcap); - printf("---- buffer (after sealing) ----\n"); - printf("sealed: %#p\n", buffer); - - buffer = cheri_unseal(buffer, sealcap); - printf("---- buffer (after unsealing) ----\n"); - printf("unsealed: %#p\n", buffer); - - free(buffer); - return 0; - } +As shown above, the address range of the OS canonical sealer is `[0x4, 0x2000)`, and its current +offset is `0x0`. Therefore, in this example, we can derive our own sealer by change the offset of +the root sealer: +.. code-block:: c + :emphasize-lines: 5-6 + void * get_derived_sealer() { + static void * sealer = NULL; + if (!sealer) { + void * root_sealer = get_system_sealer(); + size_t offset = arc4random() % cheri_length_get(root_sealer); + sealer = cheri_offset_set(root_sealer, offset); + } + return sealer; + } + + +And now we can seal the secret with the derived sealer in the main function: + +.. code-block:: c + :emphasize-lines: 6,24-25 + + int main() { + char * buf = get_secret(); + printf("[main] buf: %#p, valid: %d\n", buf, cheri_is_valid(buf)); + // we can seal the capability to prevent tampering using `cheri_seal` + void * sealer = get_derived_sealer(); + printf("[main] sealer: %#p, valid: %d\n", sealer, cheri_is_valid(sealer)); + void * sealed = cheri_seal(buf, sealer); + printf("[main] sealed: %#p, valid: %d\n", sealed, cheri_is_valid(sealed)); + + // we can unseal the capability with the original sealer + char * unsealed = (char *)cheri_unseal(sealed, sealer); + printf("[after] unsealed: %#p, %s\n", unsealed, unsealed); + + // pass the sealed capability to an untrusted function + untrusted_3rd_party_func(sealed); + } + + void untrusted_3rd_party_func(void * data) { + printf("[untrusted func] sealed: %#p, valid: %d\n", data, cheri_is_valid(data)); + + void * root_sealer; + size_t sealcap_size = sizeof(root_sealer); + sysctlbyname("security.cheri.sealcap", &root_sealer, &sealcap_size, NULL, 0); + data = cheri_unseal(data, root_sealer); + printf("[untrusted func] read as char *: %s\n", (char *)data); + } + + +If we build and run the program now, the untrusted function won't be able to use the +default root sealer to unseal the capability we passed to it, and result in a `SIGPROT` +fault: + +.. code-block:: bash + :emphasize-lines: 6,10 + + $ clang-morello -march=morello+c64 -mabi=purecap \ + -Xclang -morello-vararg=new \ + seal.c -o seal + $ ./seal + [main] buf: 0x40838000 [rwRW,0x40838000-0x40838020], valid: 1 + [main] sealer: 0x170c [,0x4-0x2000], valid: 1 + [main] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + [after] unsealed: 0x40838000 [rwRW,0x40838000-0x40838020], Shh! This is a secret! + [untrusted func] sealed: 0x40838000 [rwRW,0x40838000-0x40838020] (sealed), valid: 1 + In-address space security exception (core dumped)