Skip to content

link(ELF): a symbol defined inside .init_array/.fini_array reads a wrong value once the entry-stub writers strip the arrays #1728

Description

@davidgmbb

Summary

Buster's ELF writers that synthesize an entry stub call constructors and destructors directly, and take the two array sections off the object first (link_initializer_plan_build). Any symbol defined inside those arrays keeps its section and offset but loses its storage. Code that reads the entry through that symbol gets a wrong value, with exit 0. GNU ld keeps the array and the read is correct.

Since buster14a/buster#1716 (#1276), __attribute__((section(".init_array"))) places C variables there, so ordinary C source reaches this directly.

Reproduction

At PR head cc3fd4d, on x86-64 Linux (Ubuntu 24.04), with a Release ide built with Clang 18.1.3 under the hosted bootstrap exception:

#include <stdio.h>
static int ran;
static void early(void) { ran = 1; }
__attribute__((section(".init_array"), used)) void (*early_ptr)(void) = early;
int main(void) { printf("ran=%d ptr_ok=%d\n", ran, early_ptr == early); return 0; }
ide cc iav2.c -o a && ./a                                     # ran=1 ptr_ok=0
ide cc -c iav2.c -o iav2.o && gcc -no-pie iav2.o -o b && ./b  # ran=1 ptr_ok=1

The constructor itself runs in both cases. Only the read through early_ptr diverges.

First divergence

  • link_initializer_plan_build (link.c) copies the object with sections[INIT_ARRAY]/[FINI_ARRAY] emptied (data = {0}, virtual_size = 0), and drops their relocations.
  • Symbols whose section is one of those kinds are left untouched. Their value is an offset into a section that is no longer laid out, so the writers resolve them to an address that holds something else.
  • The same applies to foreign objects that define a symbol inside an initializer array. That is plausible for hand-written assembly or linker-set-style code, but I have not reproduced it.

Repair options (undecided)

  • Keep the array bytes as ordinary relocated read-only or writable data whenever any symbol is defined inside them (or always), and still call the entries from the stub.
  • Or refuse the link with a diagnostic naming the symbol. That is not a silent wrong value, but it is less compatible.

Completion criteria

  • With Buster's linker, the program above prints ptr_ok=1, or the link is refused with a diagnostic naming early_ptr.
  • The same holds for .fini_array and for a foreign ELF object that defines a symbol in its .init_array.
  • Constructors still run exactly once, and in priority order.
  • A regression covers it, and test_all passes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions