Resolve relocation symbols through sh_link - #59
Conversation
|
Follow-up commit b3b5a0b after an adversarial review pass, hardening the error handling in
Checked and found not to be problems: no public API/ABI break (the changed constructors are package-private and take the package-private 🤖 Generated with Claude Code |
@fornwall There's a bigger issue in the code related to the sizes of types and masking so that the values stay positive and not negative. If you have tokens, have it look at: Elf{32,64}_Half u16 -> int Maybe prompting it to make sure ELF64 is supported would automatically check all of the above. I am not sure. But, I definitely see issues where fields are You can also look at: adding range guards (like above) |
| public ElfSymbol getSymbol() { | ||
| return elfFile.getSymbolTableSection().symbols[getSymbolIndex()]; | ||
| public ElfSymbol getSymbol() throws ElfException { | ||
| return getSymbolTableSection().symbols[getSymbolIndex()]; |
There was a problem hiding this comment.
Add guard here:
ElfSymbolTableSection symbolTable = getSymbolTableSection();
int symbolIndex = getSymbolIndex();
if (symbolIndex < 0 || symbolIndex >= symbolTable.symbols.length) {
throw new ElfException("Relocation symbol index out of range");
}
return symbolTable.symbols[symbolIndex];| public ElfSymbol getSymbol() { | ||
| return elfFile.getSymbolTableSection().symbols[getSymbolIndex()]; | ||
| public ElfSymbol getSymbol() throws ElfException { | ||
| return getSymbolTableSection().symbols[getSymbolIndex()]; |
There was a problem hiding this comment.
Same guard here:
int symbolIndex = getSymbolIndex();
if (symbolIndex < 0 || symbolIndex >= symbolTable.symbols.length) | @@ -23,4 +23,31 @@ public class ElfSymbolTableSection extends ElfSection { | |||
| symbols[i] = new ElfSymbol(parser, symbolOffset, header.sh_type); | |||
There was a problem hiding this comment.
Recommend changing API here to:
symbols[i] = new ElfSymbol(parser, symbolOffset, header);Then store the header in the ELFSymbol class:
ElfSymbol(ElfParser parser, long offset, ElfSectionHeader sectionHeader) {
this.sectionHeader = sectionHeader;
this.section_type = sectionHeader.sh_type;
}
public String getName() throws ElfException {
// Check to make sure this symbol has a name.
if (st_name == 0) return null;
return getStringTableSection().get(st_name);
}
public ElfStringTable getStringTableSection() throws ElfException {
return ElfStringTable.linkedFrom(elfHeader, sectionHeader);
}Can also add such a method ElfSymbolTableSection::getStringTableSection() to this class.
01c039c to
9b4a64f
Compare
Resolve relocation symbol indexes through the symbol table selected by the containing section sh_link, and resolve symbol names through their linked string tables. Represent unsigned ELF fields with wider Java types, handle extended header counts, and validate table sizes, indexes, and file bounds before allocation or lookup. Fixes #56. Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
9b4a64f to
909f901
Compare
Throw instead of silently misparsing, and stop rejecting files that can still be read: - Reading the extended header counts requires an initial section header to read them from, so throw when e_shoff is zero instead of parsing the ELF header itself as a section header. - Throw an ElfException instead of leaking a ClassCastException when the section pointed out by e_shstrndx is not a string table, which includes it being SHN_UNDEF. - Ignore a trailing partial entry in symbol, relocation and dynamic sections instead of rejecting the whole section, as only whole entries are read anyway. The entry size is still required to be non-zero, so that the error message only names it when it is the invalid field. Also merge the duplicated note parsing in the ElfNoteSection constructor and readNotes() into a shared readNote(), which had drifted apart into validating differently. Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
|
@dwalluck Claude had a go at fixing things here. The first commit has been updated and squashed, and then a commit appended. Can you re-review/check this PR now? |
| while (true) { | ||
| if (index == 0) return null; | ||
| ElfSymbol symbol = symbolTable.symbols[index]; | ||
| if (index >= symbolTable.symbols.length || index >= chain.length) { | ||
| throw new ElfException("Hash symbol index out of range: " + index); | ||
| } | ||
| ElfSymbol symbol = symbolTable.symbols[(int) index]; | ||
| if (name.equals(symbol.getName())) return symbol; | ||
| index = chain[index]; | ||
| index = chain[(int) index]; | ||
| } |
There was a problem hiding this comment.
The range check is right, but it's still missing a bound on while(true) loop.
|
|
||
| while (buf.hasRemaining()) { | ||
| if (channel.read(buf) == -1) { | ||
| break; |
There was a problem hiding this comment.
Should throw here instead of break I think.
| // Note that e_shstrndx being SHN_UNDEF, which means that there is no section name string table, ends | ||
| // up here as well, since section 0 is not a string table. | ||
| ElfSection section = getSection(e_shstrndx); |
There was a problem hiding this comment.
Table is optional though, so I wonder about returning null instead of throwing if (e_shstrndx == 0) return null;. And in getName() similarly if (tbl == null) return null;
| int numEntries = ElfFile.arraySize(header.sh_size / header.sh_entsize, "dynamic entry count"); | ||
|
|
||
| // Except for the DT_NULL element at the end of the array, and the relative order of DT_NEEDED elements, entries | ||
| // may appear in any order. So important to use lazy evaluation to only evaluating e.g. DT_STRTAB after the | ||
| // necessary DT_STRSZ is read. | ||
| loop: | ||
| for (int i = 0; i < numEntries; i++) { | ||
| long d_tag = parser.readIntOrLong(); | ||
| final long d_val_or_ptr = parser.readIntOrLong(); | ||
| long d_tag = parser.readSignedIntOrLong(); | ||
| final long d_val_or_ptr = parser.readUnsignedIntOrLong(); |
There was a problem hiding this comment.
So numEntries is fixed now, but the loop reads without checking ei_class so I think this is wrong on 64-bit.
| public void skip(int bytesToSkip) { | ||
| int target = mappedByteBuffer.position() + bytesToSkip; |
There was a problem hiding this comment.
Need to range check skip() too, just like seek().
| } | ||
| try { | ||
| this.mappedByteBuffer.position((int) (offset)); // we may be limited to sub-4GB mapped files | ||
| this.mappedByteBuffer.position((int) offset); |
There was a problem hiding this comment.
Minor, but you're repositioning the caller's buffer, should call duplicate().
Fixes #56.
ElfRelocation#getSymbol()andElfRelocationAddend#getSymbol()resolved the symbol index againstElfFile#getSymbolTableSection(), i.e. the firstSHT_SYMTABsection of the file. A relocation entry symbol index is instead relative to the symbol table given by thesh_linkfield of the section containing it.For dynamic relocation sections (
.rel.dyn,.rela.dyn,.rel.plt,.rela.plt), which link to.dynsym, this meant:.symtab— e.g. forandroid_arm_libncurses, the first.rel.pltentry resolved to$arather than__cxa_atexit;.symtab, such aslinux_amd64_bindash, failed with aNullPointerException.Changes
sh_link.getSymbolTableSection()on relocation entries and sections, withElfExceptionerrors for invalid links or symbol indexes.sh_link.Relocations in relocatable object files still link to
.symtaband resolve as before.Compatibility
This PR intentionally changes the public API and JVM ABI to represent unsigned ELF fields correctly. Public fields including
ElfFile.e_machine,ElfFile.e_flags,ElfSectionHeader.sh_type,ElfSectionHeader.sh_link,ElfSegment.p_type, and severalElfSymbolandElfNotefields change fromshortorinttointorlong.ElfRelocation#getSymbolIndex()andElfRelocationAddend#getSymbolIndex()change their return type frominttolong.Existing binaries compiled against 0.12.0 can encounter
NoSuchFieldErrororNoSuchMethodError, and some source consumers will require narrowing conversions or updatedswitchcode. Consumers must recompile and adapt to the widened types; the next release containing this change must be treated as API-incompatible.🤖 Generated with Claude Code and OpenAI Codex