package release import ( "bytes" "encoding/binary" "errors" "sort" ) // ErrELFBounds is every way CheckELFBounds refuses an artefact. var ErrELFBounds = errors.New("release: artefact is not a bounded linux-arm64 ELF") // ELF limits for CheckELFBounds. A Go linux-arm64 binary has a few dozen // sections and about ten program headers; these leave ample room and bound // the metadata any later parser can be made to walk. const ( elfMaxSections = 256 elfMaxPrograms = 64 elfHeaderSize = 64 elfPhentSize = 56 elfShentSize = 64 elfMachAArch64 = 183 elfStrTab = 3 // SHT_STRTAB elfNoBits = 8 // SHT_NOBITS: no file contents elfMaxNameTab = 64 << 10 elfMaxName = 256 elfCompressed = 0x800 // SHF_COMPRESSED elfXIndex = 0xffff ) // CheckELFBounds is the bounded pre-check run before debug/buildinfo parses // an artefact in a root process (controller ruling F1). It reads only the // fixed-size headers, by offset, with every count and offset checked // against len(b), and accepts only: // - ELF64, little-endian, version 1, EM_AARCH64; // - a program header table of at most 64 entries of 56 bytes, and a // section header table of 1 to 256 entries of 64 bytes, each inside the // file, not overlapping each other or the ELF header, with no extended // numbering (e_shnum 0, e_shstrndx 0xffff); // - every segment's file range inside the file; // - every section's file range inside the file, no section flagged // SHF_COMPRESSED (whose decompression is unbounded by the file size), // and no two sections with contents overlapping; // - a section-name table (e_shstrndx) of type SHT_STRTAB, 1 byte to // 64 KiB, every section's name offset inside it, and every name at // most 256 bytes up to its NUL. // // debug/elf copies each section's name out of the name table, so names // that all point at one huge string would multiply it by the section count // (Astra's re-review, controller ruling F1). With the caps, the names it // copies are at most 256 x 256 bytes, no section is decompressed, and every // range lies inside the bounded file. No further parser hardening is in // scope for this branch. func CheckELFBounds(b []byte) error { n := uint64(len(b)) if n < elfHeaderSize || string(b[:4]) != "\x7fELF" || b[4] != 2 || b[5] != 1 || b[6] != 1 { return ErrELFBounds } le := binary.LittleEndian if le.Uint16(b[18:]) != elfMachAArch64 || le.Uint32(b[20:]) != 1 || le.Uint16(b[52:]) != elfHeaderSize { return ErrELFBounds } phoff, shoff := le.Uint64(b[32:]), le.Uint64(b[40:]) phentsize, phnum := uint64(le.Uint16(b[54:])), uint64(le.Uint16(b[56:])) shentsize, shnum := uint64(le.Uint16(b[58:])), uint64(le.Uint16(b[60:])) shstrndx := uint64(le.Uint16(b[62:])) if phnum > elfMaxPrograms || phnum > 0 && phentsize != elfPhentSize || shnum == 0 || shnum > elfMaxSections || shentsize != elfShentSize || shstrndx == elfXIndex || shstrndx >= shnum { return ErrELFBounds } within := func(off, size uint64) bool { return off <= n && size <= n-off } phEnd, shEnd := phoff+phnum*elfPhentSize, shoff+shnum*elfShentSize if phnum > 0 && (!within(phoff, phnum*elfPhentSize) || phoff < elfHeaderSize) { return ErrELFBounds } if !within(shoff, shnum*elfShentSize) || shoff < elfHeaderSize || phnum > 0 && shoff < phEnd && phoff < shEnd { return ErrELFBounds } for i := uint64(0); i < phnum; i++ { h := b[phoff+i*elfPhentSize:] if !within(le.Uint64(h[8:]), le.Uint64(h[32:])) { // p_offset, p_filesz return ErrELFBounds } } type span struct{ off, end uint64 } var spans []span for i := uint64(0); i < shnum; i++ { h := b[shoff+i*elfShentSize:] typ, flags, off, size := le.Uint32(h[4:]), le.Uint64(h[8:]), le.Uint64(h[24:]), le.Uint64(h[32:]) if flags&elfCompressed != 0 { return ErrELFBounds } if typ == elfNoBits || size == 0 { continue } if !within(off, size) { return ErrELFBounds } spans = append(spans, span{off, off + size}) } names := b[shoff+shstrndx*elfShentSize:] if le.Uint32(names[4:]) != elfStrTab { return ErrELFBounds } nameOff, nameSize := le.Uint64(names[24:]), le.Uint64(names[32:]) if nameSize == 0 || nameSize > elfMaxNameTab || !within(nameOff, nameSize) { return ErrELFBounds } table := b[nameOff : nameOff+nameSize] for i := uint64(0); i < shnum; i++ { at := uint64(le.Uint32(b[shoff+i*elfShentSize:])) if at >= nameSize { return ErrELFBounds } name := table[at:] if len(name) > elfMaxName+1 { name = name[:elfMaxName+1] } if bytes.IndexByte(name, 0) < 0 { return ErrELFBounds } } sort.Slice(spans, func(i, j int) bool { return spans[i].off < spans[j].off }) for i := 1; i < len(spans); i++ { if spans[i].off < spans[i-1].end { return ErrELFBounds } } return nil }