[PATCH] D39548: [ELF] Set the section size on the section start symbols

Alexander Richardson via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 6 10:19:41 PST 2017


arichardson added a comment.

In https://reviews.llvm.org/D39548#916728, @peter.smith wrote:

> Can you share a bit more about how CHERI uses the symbol size? Is it only for known linker generated Sections like in your example above, or to support arbitrary user mysection_start symbols? Is the mysection_start symbol special cased within CHERI or is it just an arbitrary ELF Symbol.


We don't do any special casing based on symbol names, we only look at the information from the symbol table. I think having special cases based on the symbol name is very fragile so I'd rather set a size on all linker defined symbols that are used as dereferenceable pointers.

> I'm just a bit curious about the implications of setting the symbol size, for example if this is for special sections like .dynamic we would only expect the _start and _end, and _DYNAMIC symbols to be defined, in an arbitrary mysection_start there could be multiple other symbols defined as well and having the symbol size set to the section size would mean its [0, size)  range would overlap all other symbols in the section, this could potentially confuse other ELF processing tools that might skip over functions/data based on Symbol Size. I'm also curious as to how the symbol size is used? Does the OS/loader require the static symbol table to be present, what if it has been stripped?

I hadn't thought about the potential to confuse other ELF processing tools. However, wouldn't that also break on code like this?

  .global foo_with_setup
  .ent foo_with_setup
  foo_with_setup:
    ....
    ....
  foo_fast:
   ....
   ....
  .end foo_with_setup
  
  jal foo_with_setup
  ...
  jal foo_fast

In CHERI each pointer (capability) is architecturally 256 bits (compressed to 128 in memory): 64 bit base address + 64 bit offset + 64 bit length and <64bits attributes (read data/store data/executable/load capability/store capability, etc.). During the initial startup we have to initialize all global capabilities using the information from the ELF symbols.
Currently we generate a special ELF section `__cap_relocs` containing a capability "relocations" for every global pointer.  For historic reasons we don't use ELF relocations but an array of structs that is processed by crt_init_globals() <https://github.com/CTSRD-CHERI/cheribsd/blob/master/lib/csu/cheri/crt1.c#L96> in the initial startup code.
This separate section also has the advantage that we don't need any symbol tables at runtime but can just loop over this array containing addresses resolved at static link time.
We need sizes on every ELF symbol that can be used as a pointer in order to set the correct runtime-bounds on the resulting capability so that we can find all bounds violations.

Say we have the following code:

  char x[20];
  char* y = &x[20];
  
  int foo(void) {
     char c = y[-1]; // okay
     c = y[0];  // trap at runtime
    return c;
  }
  
  __attribute__((weak)) extern Elf_Dyn* _DYNAMIC;
  int parse_dynamic(void) {
    Elf_Dyn* dynp = _DYNAMIC;
    for (; dynp->d_tag != DT_NULL; dynp++) {
      // ...
    }
    return 0;
  }

Currently we can generate a `__cap_relocs` section with the following contents (the load address will be added to these addresses before the globals are initialized):

  CAPABILITY RELOCATION RECORDS:
  0x0000000000020000	Base: x (0x0000000000040000)	Offset: 0x0000000000000014	Length: 0x0000000000000014	Permissions: 0x00000000
  0x0000000000030000	Base: y (0x0000000000020000)	Offset: 0x0000000000000000	Length: 0x0000000000000010	Permissions: 0x00000000
  0x0000000000030010	Base: _DYNAMIC (0x00000000000002b0)	Offset: 0x0000000000000000	Length: 0x0000000000000000	Permissions: 0x00000000
  SYMBOL TABLE:
  0000000000000000         *UND*		 00000000 
  00000000000002b0         .dynamic		 00000000 .hidden _DYNAMIC
  0000000000028010         .got		 00000000 .hidden _gp
  0000000000030000 l    d  .cap_table		 00000000 .hidden .cap_table
  0000000000030000 l       .cap_table		 00000010 .hidden y at CAPTABLE
  0000000000030010 l       .cap_table		 00000010 .hidden _DYNAMIC at CAPTABLE
  0000000000010000 g     F .text		 0000001c foo
  0000000000010020 g     F .text		 00000024 parse_dynamic
  0000000000040000 g       .bss		 00000014 x
  0000000000020000 g       .data		 00000010 y

The problem here is because LLD says _DYNAMIC has size 0 we also create a capability at runtime that has a base of _DYNAMIC but with a length of zero so we will crash as soon as we load a single byte from this capability.
In order to not crash while iterating over _DYNAMIC we need the __cap_reloc for _DYNAMIC to be this instead:

  CAPABILITY RELOCATION RECORDS:
  0x0000000000030010	Base: _DYNAMIC (0x00000000000002b0)	Offset: 0x0000000000000000	Length: 0x0000000000000120	Permissions: 0x00000000
  SYMBOL TABLE:
  00000000000002b0         .dynamic		 00000120 .hidden _DYNAMIC

To generate those values we use `Symbol->getSize<ELFT>()` which currently returns zero for `_DYNAMIC` and `__init_array_start`, etc.
So far I have found this to be a problem with `_DYNAMIC`, the init+fini array start symbols as well as sections that have a c indentifier so code assumes it can simply dereference `__start_foo`.

> On the assumption that there is a good reason for CHERI to use the symbol size I have some suggestions for the code.
> 
> - Given that addOptionalRegular is only used for start and end symbols and only uses the (now two) special cases of 0 and -1, I think we should rename the function to make it more specific to the purpose it is being used for.
> - Would it be better to use a similar trick for Size as Value, for example if Size is -1 and symbol is defined with respect to an OutputSection then Size is Size of OutputSection? You'd then not need to track the state of the section symbols and have a potentially fragile one off update.

I like the idea of using Size == -1 as the output section size as this avoids the fragile state update. I will update the patch if you agree that it makes sense include this change in upstream LLD.

I hope my explanation made some sense. I do realize we compared to most architectures CHERI is rather exotic but I feel like having sizes defined on symbols that are generally used as pointers would be a good patch to upstream.


https://reviews.llvm.org/D39548





More information about the llvm-commits mailing list