Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ELF loader crashes on arm-linux-gnueabi-gcc binary (Found section header that overlaps with program header.) #302

Open
RyanGlScott opened this issue Jul 13, 2022 · 3 comments · May be fixed by #332
Labels
arch:aarch32 AArch32 (32 bit ARM) issues bug

Comments

@RyanGlScott
Copy link
Contributor

Take the following program (adapted from #301 (comment)):

// test.c
#include <stdint.h>

uint64_t x = 0;

int __attribute__((noinline)) test_strd() {
  x = 42;
  return x == 42;
}

int main() {
  test_strd();
  return 0;
}

And compile it with arm-linux-gnueabi-gcc like so:

$ arm-linux-gnueabi-gcc -nostartfiles -O2 -static test.c -o test.exe

And run it through this test harness:

-- Bug.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Main (main) where

import           Control.Lens ( (^.) )
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ElfEdit as Elf
import qualified Data.Foldable as F
import qualified Data.Map as Map
import           Data.Maybe ( mapMaybe )
import qualified Data.Parameterized.Classes as PC
import qualified Data.Parameterized.Nonce as PN
import           Data.Parameterized.Some ( Some(..) )
import           Data.Proxy ( Proxy(..) )

import qualified Language.ASL.Globals as ASL
import qualified Data.Macaw.Architecture.Info as MAI

import           Data.Macaw.AArch32.Symbolic ()
import qualified Data.Macaw.ARM as MA
import qualified Data.Macaw.ARM.ARMReg as MAR
import qualified Data.Macaw.Discovery as M
import qualified Data.Macaw.Symbolic as MS
import qualified Data.Macaw.Symbolic.Testing as MST
import qualified What4.Config as WC
import qualified What4.Expr as WE
import qualified What4.Expr.Builder as WEB
import qualified What4.Interface as WI
import qualified What4.ProblemFeatures as WPF
import qualified What4.Solver as WS

import qualified Lang.Crucible.Backend as CB
import qualified Lang.Crucible.Backend.Online as CBO
import qualified Lang.Crucible.Simulator as CS
import qualified Lang.Crucible.LLVM.MemModel as LLVM

main :: IO ()
main = do
  -- These are pass/fail in that the assertions in the "pass" set are true (and
  -- the solver returns Unsat), while the assertions in the "fail" set are false
  -- (and the solver returns Sat).
  let passRes = MST.SimulationResult MST.Unsat
  mkSymExTest passRes "test.exe"

hasTestPrefix :: Some (M.DiscoveryFunInfo arch) -> Maybe (BS8.ByteString, Some (M.DiscoveryFunInfo arch))
hasTestPrefix (Some dfi) = do
  bsname <- M.discoveredFunSymbol dfi
  if BS8.pack "test_" `BS8.isPrefixOf` bsname
    then return (bsname, Some dfi)
    else Nothing

mkSymExTest :: MST.SimulationResult -> FilePath -> IO ()
mkSymExTest expected exePath = do
  bytes <- BS.readFile exePath
  case Elf.decodeElfHeaderInfo bytes of
    Left (_, msg) -> fail ("Error parsing ELF header from file '" ++ show exePath ++ "': " ++ msg)
    Right (Elf.SomeElf ehi) -> do
      case Elf.headerClass (Elf.header ehi) of
        Elf.ELFCLASS32 ->
          symExTestSized expected ehi MA.arm_linux_info
        Elf.ELFCLASS64 -> fail "64 bit ARM is not supported"

symExTestSized :: MST.SimulationResult
               -> Elf.ElfHeaderInfo 32
               -> MAI.ArchitectureInfo MA.ARM
               -> IO ()
symExTestSized expected ehi archInfo = do
   (mem, discState) <- MST.runDiscovery ehi MST.toAddrSymMap archInfo
   let funInfos = Map.elems (discState ^. M.funInfo)
   let testEntryPoints = mapMaybe hasTestPrefix funInfos
   F.forM_ testEntryPoints $ \(name, Some dfi) -> do
     putStrLn ("Testing " ++ BS8.unpack name ++ " at " ++ show (M.discoveredFunAddr dfi))
     Some (gen :: PN.NonceGenerator IO t) <- PN.newIONonceGenerator
     sym <- WEB.newExprBuilder WEB.FloatRealRepr WE.EmptyExprBuilderState gen
     CBO.withYicesOnlineBackend sym CBO.NoUnsatFeatures WPF.noFeatures $ \bak -> do
       -- We are using the z3 backend to discharge proof obligations, so
       -- we need to add its options to the backend configuration
       let solver = WS.z3Adapter
       let backendConf = WI.getConfiguration sym
       WC.extendConfig (WS.solver_adapter_config_options solver) backendConf

       execFeatures <- MST.defaultExecFeatures (MST.SomeOnlineBackend bak)
       let Just archVals = MS.archVals (Proxy @MA.ARM) Nothing
       let extract = armResultExtractor archVals
       let ?memOpts = LLVM.defaultMemOptions
       simRes <- MST.simulateAndVerify solver WS.defaultLogData bak execFeatures archInfo archVals mem extract discState dfi
       if expected == simRes
         then putStrLn "Test passed"
         else fail $ "Expected " ++ show expected ++ ", but got " ++ show simRes

-- | ARM functions with a single scalar return value return it in %r0
--
-- Since all test functions must return a value to assert as true, this is
-- straightforward to extract
armResultExtractor :: ( CB.IsSymInterface sym
                      )
                   => MS.ArchVals MA.ARM
                   -> MST.ResultExtractor sym MA.ARM
armResultExtractor archVals = MST.ResultExtractor $ \regs _sp _mem k -> do
  let re = MS.lookupReg archVals regs (MAR.ARMGlobalBV (ASL.knownGlobalRef @"_R0"))
  k PC.knownRepr (CS.regValue re)

It will crash with the following error:

$ runghc-9.2 Bug.hs
Bug.hs: Found section header that overlaps with program header.
CallStack (from HasCallStack):
  error, called at src/Data/Macaw/Memory/ElfLoader.hs:1175:13 in macaw-base-0.3.15.6-inplace:Data.Macaw.Memory.ElfLoader

Curiously, the same error does not occur if you instead compile this program with musl-gcc:

$ armv7m-linux-musleabi-gcc -nostartfiles -O2 -static test.c -o test.exe
$ runghc-9.2 Bug.hs
Testing test_strd at segment1+0x120
Test passed
@travitch
Copy link
Contributor

We should check if there really is an overlap or if that is a bug in elf-edit. That said, there is nothing in the ELF standard that prohibits sections from overlapping, so we shouldn't be erroring out if it happens

@RyanGlScott
Copy link
Contributor Author

Some additional printf debugging reveals these as the values of the respective header offsets:

Bug.hs: Found section header that overlaps with program header.
phdrOffset: 0x110
shdr_start: 0x10c

From the output of readelf, we have the following program headers:

$ readelf -l test.exe 

Elf file type is EXEC (Executable file)
Entry point 0x100d8
There are 4 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00010000 0x00010000 0x0010c 0x0010c R E 0x10000
  LOAD           0x000110 0x00020110 0x00020110 0x00000 0x00008 RW  0x10000
  NOTE           0x0000b4 0x000100b4 0x000100b4 0x00024 0x00024 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .text 
   01     .bss 
   02     .note.gnu.build-id 
   03

And the following section headers:

$ readelf -S test.exe 
There are 9 section headers, starting at offset 0x370:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.gnu.build-i NOTE            000100b4 0000b4 000024 00   A  0   0  4
  [ 2] .text             PROGBITS        000100d8 0000d8 000034 00  AX  0   0  4
  [ 3] .bss              NOBITS          00020110 000110 000008 00  WA  0   0  8
  [ 4] .comment          PROGBITS        00000000 00010c 00002b 01  MS  0   0  1
  [ 5] .ARM.attributes   ARM_ATTRIBUTES  00000000 000137 00002a 00      0   0  1
  [ 6] .symtab           SYMTAB          00000000 000164 000160 10      7  11  4
  [ 7] .strtab           STRTAB          00000000 0002c4 000059 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 00031d 000052 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

So the program header offset 0x110 corresponds to the first LOAD, and the section header offset corresponds to .comment. Unless, I'm missing something, this looks like legitimate overlap to me.

@RyanGlScott
Copy link
Contributor Author

Then again, it's not sufficient to just drop the validity check in the these lines:

let l = IMap.toList $ IMap.intersecting shdrMap (IntervalCO phdrOffset phdrEnd)
forM_ l $ \(i, elfIdx) -> do
case i of
IntervalCO shdr_start _ -> do
when (phdrOffset > shdr_start) $ do
error "Found section header that overlaps with program header."

As the following lines are:

let sec_offset = fromIntegral $ shdr_start - phdrOffset
addr <- case resolveSegmentOff seg sec_offset of
Just addr -> pure addr
Nothing -> error $ "insertElfSegment: Failed to resolve segment offset at "
++ show sec_offset

If phdrOffset > shdr_start, then sec_offset will underflow, which certainly doesn't seem right. Perhaps this is a sign that we need a different approach to matching up program headers and section headers besides Data.IntervalMap.intersecting?

danmatichuk added a commit that referenced this issue Jul 18, 2023
…er error

This error is caused by a bug in the IntervalMap implementation prior to
0.6.2, which had inconsistent semantics for empty intervals
(see: bokesan/IntervalMap#9)

fixes #302
danmatichuk added a commit that referenced this issue Jul 18, 2023
…er error

This error is caused by a bug in the IntervalMap implementation prior to
0.6.2, which had inconsistent semantics for empty intervals
(see: bokesan/IntervalMap#9)

fixes #302
@danmatichuk danmatichuk linked a pull request Jul 18, 2023 that will close this issue
danmatichuk added a commit that referenced this issue Jul 18, 2023
…er error

This error is caused by a bug in the IntervalMap implementation prior to
0.6.2, which had inconsistent semantics for empty intervals
(see: bokesan/IntervalMap#9)

fixes #302
danmatichuk added a commit that referenced this issue Jul 18, 2023
…er error

This error is caused by a bug in the IntervalMap implementation prior to
0.6.2, which had inconsistent semantics for empty intervals
(see: bokesan/IntervalMap#9)

fixes #302
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch:aarch32 AArch32 (32 bit ARM) issues bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants