0

I have been programming with ARM64 assembly, and my goal has been the following:

  1. Reserve a register to store sensitive information (e.g., randomly generated key)
  2. Access this reserved register to load the sensitive information and use it for the purposes such as encryption and decryption
  3. Do this multiple times without jeopardizing the values stored in the register (basically, I hope that this reserved register is not wiped away and will keep its stored value until I specifically wish to clear it).

These links have gave me some good ideas and starting points:

However, I am facing this weird undefined behavior and trying to figure out what is happening, which prompted me to write this post.

I have these two codes:

The first code is a main.c

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include "test_lib.h"

register uint64_t rand_key asm ("x11");

int main(int argc, char const *argv[]) {

    gen_rand_key();
    uint64_t test = rand_key;
    printf("Random Key:\t\t0x%lx\n", test);
    printf("Random Key:\t\t0x%lx\n", rand_key);

    return 0;
}

The second is a test_lib.c compiled as a shared library for my main.c program.

#include <stdint.h>
uint64_t gen_rand_key() {
    asm volatile (        
        "eor x11, x11, x11\n\t"
        "mrs x11, rndr\n\t"
        : 
        : 
        : "x11"
    );
}
// rndr instruction is only available from ARMv8.5, 
// this instruction can be replaced with any other instructions that perform some bit manipulation.

Here is the output upon executing the main.c:

~ ➤ LD_LIBRARY_PATH=./ ./main.out                            
Random Key:             0x4fee674452b68da3
Random Key:             0xffffcc5ebd48

To explain what is happening above, I first call gen_rand_key() function from the test_lib.so which is going to use the rndr instruction (https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/RNDR--Random-Number), to generate a random number 0x4fee674452b68da3 and store this value into x11 register. After that, I use the rand_key variable to access the x11 register and store this value into the test variable. Next, I print test and then rand_key in that order. (Just as a side note), I have specifically created a shared library to make this functionality portable across different applications

Now I see that the test value outputs the correct randomly generated number, but when I use the rand_key variable, I expect the value 0x4fee674452b68da3 to show again, but I get 0xffffcc5ebd48.

Therefore, I have been trying to figure out why this weird behavior is happening, but to no avail. I guess that as soon as this x11 register is read from accessing rand_key, this value is freed from the register? But I wasn't sure whether that was correct or not.

To summarize my questions:

Why has the x11 register value disappeared after accessing it?

Also, I'm wondering whether there is a better way to achieve the stated goals, as I feel accessing a register is a bit finicky.

Please let me know if anything I mentioned above doesn't make sense, and I will try to clarify it.

Jay
  • 373
  • 1
  • 10
  • 4
    What you are trying to achieve cannot be done without having complete control over any code running in your program. As soon as you call a libc function like `printf`, every single register may be overwritten. Try to find a different solution or write in assembly. – fuz Dec 27 '21 at 01:04
  • 3
    Whether or not you "reserve" a register in inline assembly, the calling convention still rules; so, between function calls scratch/call-clobbered registers should retain their values, but when a function call happens, there's no such guarantee. Like fuz says, for real control you need to write in actual assembly (not inline assembly); though you'll still be subject to the calling convention. (`x11` is among the scratch / call-clobbered set). – Erik Eidt Dec 27 '21 at 01:07
  • 1
    bare in mind that if you "reserve" register it will disappear from the register pool in you compilation unit making the compiler job more complicated. It will result in worse generated code. This feature is very rarely used. Last time I was using it it was AVR code when I had to keep register value between naked functions (every clock did matter in that project). – 0___________ Dec 27 '21 at 01:12
  • Thank you very much for all of the comments. I think what @fuz mentioned helped me understand. The reason is that I also noticed when I `printf` something along with the inline assembly instructions, this also caused weird undefined behavior in terms of the value stored in the `x11` register, and I was wondering why that is the case. I think maybe the better solution would be to just instrument assembly instructions using `LLVM` or something then. I will try to brainstorm a bit more, but I do appreciate any additional insights (from people who have posted or any others). – Jay Dec 27 '21 at 01:23
  • 2
    There is no encryption software that does this. You have to trust that the system that you're running on is secure. If it's secure, putting the key in a memory location is secure. Using a register is no more secure than a memory location. If your program can be compromised, the "register" is just as exposed as the memory location. (i.e.) Using a register provides _no_ extra/real security. It you're serious, get a USB security dongle of some sort. The register idea is an XY problem: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Craig Estey Dec 27 '21 at 01:26
  • 3
    You really can't win here. If you choose a call-clobbered register like `x11` then any call to a function compiled with the normal ABI (e.g. all of libc) will overwrite it. [The manual warns about this](https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc/Global-Register-Variables.html#Global-Register-Variables): "if the register is a call-clobbered register, making calls to functions that use standard ABI may lose contents of the variable..." – Nate Eldredge Dec 27 '21 at 04:54
  • 3
    And if you choose a call-preserved register, then it won't be lost when you call a library function - but the reason why is that if a library function wants to use it, it will save and restore its contents, typically *in memory*. So your goal of keeping your secret number from hitting memory is frustrated. The only way you can really achieve that is by doing your computation in a single self-contained block of assembly that calls no external functions, and overwriting the register at the end of it. – Nate Eldredge Dec 27 '21 at 04:55
  • Yup, @NateEldredge is right, the only way to have library code respect the existence of this global register variable and keep hands off would be to recompile libc and any other libs (and statically link them?) with `gcc -ffixed-x11` (https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-ffixed), instead of the default for that reg, `-fcall-used-x11`. (Or to have library code include a header that defines that register-global, so they can see it's already allocated.) – Peter Cordes Dec 27 '21 at 05:51

0 Answers0