Skip to content

Commit 9755745

Browse files
authored
Bugfix/pointer dereference bug (#57)
* LLEF Bugfixes Types.py line 277: bug in ptr_size where width needed to be in bits, not bytes. Change GoTypePointer class so that it no longer dereferences the addr parameter, which best I can tell is never given indirectly in an eface. Pointer dereferencing behaviour was depended upon by GoDataInterface, which was amended to do its own dereference where required. Also added a new type representation for a nil interface, which is where you have a line of code in Go such as: var myVar any = nil * New Black version wants new tuple unpacking syntax
1 parent ab0a0b3 commit 9755745

File tree

8 files changed

+58
-40
lines changed

8 files changed

+58
-40
lines changed

commands/golang.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def __call__(
119119
address = int(address_or_name, 0)
120120
function_mapping = go_find_func(address)
121121
if function_mapping is not None:
122-
(entry, gofunc) = function_mapping
122+
entry, gofunc = function_mapping
123123
output_line(f"{hex(entry)} - {gofunc.name} (file address = {hex(gofunc.file_addr)})")
124124
else:
125125
print_message(MSG_TYPE.ERROR, f"Could not find function containing address {hex(address)}")

common/golang/analysis.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def go_get_function_from_pc(pc: pointer, function_start: pointer, function_name:
131131
"""
132132
record = go_find_func(pc)
133133
if record is not None:
134-
(entry, gofunc) = record
134+
entry, gofunc = record
135135
return (entry, gofunc.name)
136136
return (function_start, function_name)
137137

@@ -335,7 +335,7 @@ def go_annotate_pointer_line(
335335
LLEFState.go_state.type_guesses.add(object_ptr, referenced_type_struct)
336336
unpacked_data = attempt_object_unpack(proc, object_ptr, settings, col_settings)
337337
if unpacked_data:
338-
(_, next_pointer_unpacked) = unpacked_data
338+
_, next_pointer_unpacked = unpacked_data
339339

340340
# Markup line with identified Go data type.
341341
go_type_name = color_string(referenced_type_struct.header.name, col_settings.go_type_color)
@@ -351,7 +351,7 @@ def go_annotate_pointer_line(
351351
unpacked_data = attempt_object_unpack(proc, pointer_to_annotate, settings, col_settings)
352352
object_at_pointer = None
353353
if unpacked_data:
354-
(resolved_type, object_at_pointer) = unpacked_data
354+
resolved_type, object_at_pointer = unpacked_data
355355
if object_at_pointer:
356356
line += f" {GLYPHS.RIGHT_ARROW.value} {resolved_type} {object_at_pointer}"
357357

@@ -420,7 +420,7 @@ def go_stop_hook(exe_ctx: SBExecutionContext, arch: BaseArch, settings: LLEFSett
420420
return
421421

422422
arg_registers = get_arg_registers(arch)
423-
(go_min_version, _) = LLEFState.go_state.pclntab_info.version_bounds
423+
go_min_version, _ = LLEFState.go_state.pclntab_info.version_bounds
424424
if go_min_version >= 17:
425425
# HOOK TASK 1:
426426
# Register-straddling interface/string guessing. Needs register-based calling convention (Go >= 1.17).
@@ -452,7 +452,7 @@ def go_stop_hook(exe_ctx: SBExecutionContext, arch: BaseArch, settings: LLEFSett
452452
pc = frame.GetPC()
453453
record = go_find_func(pc)
454454
if record is not None and LLEFState.go_state.moduledata_info is not None:
455-
(entry, gofunc) = record
455+
entry, gofunc = record
456456
if entry != LLEFState.go_state.prev_func:
457457
LLEFState.go_state.prev_func = entry
458458
# Either this function was just called, or we stopped in the middle of it.

common/golang/data.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,13 @@ class GoDataPointer(GoData):
239239

240240
def __str__(self) -> str:
241241
return hex(self.address)
242+
243+
244+
@dataclass(frozen=True)
245+
class GoDataNilInterface(GoData):
246+
"""
247+
An interface set to nil.
248+
"""
249+
250+
def __str__(self) -> str:
251+
return "<nil interface>"

common/golang/moduledata_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def get_name(self, type_section: bytes, name_offset: int, header: TypeHeader) ->
5555
# Check that pointer + offset doesn't exceed the end pointer for the types section.
5656
if self.types + name_offset < self.etypes:
5757
# Module data layout depends on the Go version.
58-
(go_min_version, go_max_version) = LLEFState.go_state.pclntab_info.version_bounds
58+
go_min_version, go_max_version = LLEFState.go_state.pclntab_info.version_bounds
5959
if go_min_version >= 17:
6060
length, name_offset = read_varint(type_section, name_offset)
6161
if self.types + name_offset + length <= self.etypes:
@@ -166,7 +166,7 @@ def parse(self, proc: SBProcess, data: SBData, target: SBTarget) -> Union[Module
166166

167167
offsets = None
168168

169-
(min_go, max_go) = LLEFState.go_state.pclntab_info.version_bounds
169+
min_go, max_go = LLEFState.go_state.pclntab_info.version_bounds
170170
if min_go == 7 and max_go == 7:
171171
offsets = GO_MD_7_ONLY
172172
if min_go >= 8 and max_go <= 15:

common/golang/static.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def parse_pclntab(proc: SBProcess, target: SBTarget, buf: SBData, file_addr: int
3030
err = SBError()
3131
first8bytes = buf.ReadRawData(err, 0, 8)
3232
if err.Success() and first8bytes is not None:
33-
(magic, pad, min_instr_size, ptr_size) = struct.unpack("<IHBB", first8bytes)
33+
magic, pad, min_instr_size, ptr_size = struct.unpack("<IHBB", first8bytes)
3434

3535
parser = PCLnTabParser(file_addr, magic, pad, min_instr_size, ptr_size)
3636

common/golang/type_getter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def __map_to_type(self, map_repr: str) -> Union[GoTypeMap, None]:
226226
resolved.key_type = key_type
227227
resolved.child_type = val_type
228228

229-
(go_min_version, _) = self.__version
229+
go_min_version, _ = self.__version
230230
if go_min_version < 24:
231231
# Old map type.
232232
bucket_str = (

common/golang/types.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
GoDataFloat,
2828
GoDataInteger,
2929
GoDataMap,
30+
GoDataNilInterface,
3031
GoDataPointer,
3132
GoDataSlice,
3233
GoDataString,
@@ -273,7 +274,7 @@ def get_underlying_type(self, depth: int) -> str:
273274
def extract_at(self, info: ExtractInfo, addr: pointer, dereferenced_pointers: set[pointer], depth: int) -> GoData:
274275
val = safe_read_unsigned(info, addr, info.ptr_size)
275276
if val is not None:
276-
sign_bit = 1 << (info.ptr_size - 1)
277+
sign_bit = 1 << ((info.ptr_size * 8) - 1)
277278
# convert unsigned to signed
278279
val -= (val & sign_bit) << 1
279280
return GoDataInteger(heuristic=Confidence.CERTAIN.to_float(), value=val)
@@ -454,7 +455,7 @@ class GoTypeArray(GoType):
454455
length: int
455456

456457
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[int], None]:
457-
(sub_elem, sup_slice, this_len) = struct.unpack_from("<" + info.ptr_specifier * 3, type_section, offset)
458+
sub_elem, sup_slice, this_len = struct.unpack_from("<" + info.ptr_specifier * 3, type_section, offset)
458459
self.child_addr = sub_elem
459460
self.length = this_len
460461
return [sub_elem, sup_slice]
@@ -516,7 +517,7 @@ class GoTypeChan(GoType):
516517
direction: int
517518

518519
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[int], None]:
519-
(sub_elem, direction) = struct.unpack_from("<" + info.ptr_specifier * 2, type_section, offset)
520+
sub_elem, direction = struct.unpack_from("<" + info.ptr_specifier * 2, type_section, offset)
520521
self.child_addr = sub_elem
521522
self.direction = direction
522523
return [sub_elem]
@@ -552,7 +553,7 @@ class GoTypeFunc(GoType):
552553

553554
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[int], None]:
554555
# the size of param count fields and the uncommon offset addition is NOT pointer size dependent.
555-
(num_param_in, num_param_out) = struct.unpack_from("<HH", type_section, offset)
556+
num_param_in, num_param_out = struct.unpack_from("<HH", type_section, offset)
556557

557558
# We consumed 32 bits. On 32-bit, read the next byte: on 64-bit, need 32 bits of padding.
558559
offset += info.ptr_size
@@ -625,16 +626,16 @@ class GoTypeInterface(GoType):
625626

626627
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[pointer], None]:
627628
self.methods = []
628-
(go_min_version, _) = self.version
629-
(methods_base, methods_len) = struct.unpack_from(
629+
go_min_version, _ = self.version
630+
methods_base, methods_len = struct.unpack_from(
630631
"<" + info.ptr_specifier * 2, type_section, offset + info.ptr_size
631632
)
632633
# Each method structure in the methods table is a struct of two 32-bit integers.
633634
for i in range(methods_len):
634635
imethod_ptr = methods_base + i * 8
635636
if info.types <= imethod_ptr < info.etypes:
636637
imethod_offset = imethod_ptr - info.types
637-
(name_off, type_off) = struct.unpack_from("<II", type_section, imethod_offset)
638+
name_off, type_off = struct.unpack_from("<II", type_section, imethod_offset)
638639

639640
name_off += 1
640641
if go_min_version <= 16:
@@ -692,12 +693,21 @@ def extract_at(self, info: ExtractInfo, addr: pointer, dereferenced_pointers: se
692693
fail_nicely = True
693694
type_ptr = safe_read_unsigned(info, itab_ptr + info.ptr_size, info.ptr_size)
694695

695-
# Treat this like dereferencing a typed pointer.
696696
if type_ptr is not None:
697-
header = TypeHeader()
698-
extractor = GoTypePointer(header=header, version=self.version)
699-
extractor.child_type = info.type_structs.get(type_ptr)
700-
extracted = extractor.extract_at(info, addr + info.ptr_size, dereferenced_pointers, depth)
697+
# Treat this like dereferencing a typed pointer.
698+
if type_ptr > 0:
699+
interface_type = info.type_structs.get(type_ptr)
700+
if interface_type is not None:
701+
data_pointer = safe_read_unsigned(info, addr + info.ptr_size, info.ptr_size)
702+
if data_pointer is not None:
703+
extracted = interface_type.extract_at(info, data_pointer, dereferenced_pointers, depth)
704+
else:
705+
extracted = GoDataBad(heuristic=Confidence.JUNK.to_float())
706+
else:
707+
extracted = GoDataBad(heuristic=Confidence.JUNK.to_float())
708+
else:
709+
# Nil interface
710+
extracted = GoDataNilInterface(heuristic=Confidence.HIGH.to_float())
701711

702712
if extracted is None:
703713
if fail_nicely:
@@ -715,7 +725,7 @@ class GoTypeMap(GoType):
715725
bucket_type: Union[GoType, None]
716726

717727
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[pointer], None]:
718-
(self.key_addr, self.child_addr, self.bucket_addr) = struct.unpack_from(
728+
self.key_addr, self.child_addr, self.bucket_addr = struct.unpack_from(
719729
"<" + info.ptr_specifier * 3, type_section, offset
720730
)
721731
self.key_type = None
@@ -740,7 +750,7 @@ def extract_at(self, info: ExtractInfo, addr: pointer, dereferenced_pointers: se
740750
extracted: Union[GoData, None] = None
741751

742752
if self.bucket_type is not None:
743-
(go_min_version, _) = self.version
753+
go_min_version, _ = self.version
744754
# Minimum version is only lifted to 24 if we are sure the new map implementation is being used
745755
# (the programmer can choose to use the old version even in 1.24).
746756
parser: Union[SwissMapParser, NoSwissMapParser]
@@ -778,23 +788,22 @@ def extract_at(self, info: ExtractInfo, addr: pointer, dereferenced_pointers: se
778788
extracted: Union[GoData, None] = None
779789

780790
if self.child_type:
781-
child_ptr = safe_read_unsigned(info, addr, info.ptr_size)
782-
if child_ptr is not None:
783-
if child_ptr > 0:
784-
if child_ptr not in dereferenced_pointers:
785-
dereferenced_pointers.add(child_ptr)
791+
if addr is not None:
792+
if addr > 0:
793+
if addr not in dereferenced_pointers:
794+
dereferenced_pointers.add(addr)
786795
# changes to dereferenced_pointers are reflected everywhere, so we'll never dereference again
787796
# in this extraction.
788797
# this is good because we can reduce duplication of displayed information.
789-
dereferenced = self.child_type.extract_at(info, child_ptr, dereferenced_pointers, depth)
798+
dereferenced = self.child_type.extract_at(info, addr, dereferenced_pointers, depth)
790799
if not isinstance(dereferenced, GoDataBad):
791800
extracted = dereferenced
792801
else:
793802
# Then this pointer is not of this type - either memory does not exist, or data is illegal.
794-
extracted = GoDataPointer(heuristic=Confidence.JUNK.to_float(), address=child_ptr)
803+
extracted = GoDataPointer(heuristic=Confidence.JUNK.to_float(), address=addr)
795804
else:
796805
# Circular references. Slightly downgrade confidence.
797-
extracted = GoDataUnparsed(heuristic=Confidence.HIGH.to_float(), address=child_ptr)
806+
extracted = GoDataUnparsed(heuristic=Confidence.HIGH.to_float(), address=addr)
798807
else:
799808
# A valid, but null, pointer. Of course these come up - but downgrade the confidence.
800809
extracted = GoDataPointer(heuristic=Confidence.MEDIUM.to_float(), address=0)
@@ -956,16 +965,16 @@ class GoTypeStruct(GoType):
956965
fields: list[GoTypeStructField]
957966

958967
def populate(self, type_section: bytes, offset: int, info: PopulateInfo) -> Union[list[pointer], None]:
959-
(go_min_version, go_max_version) = self.version
960-
(_, fields_addr, fields_len) = struct.unpack_from("<" + info.ptr_specifier * 3, type_section, offset)
968+
go_min_version, go_max_version = self.version
969+
_, fields_addr, fields_len = struct.unpack_from("<" + info.ptr_specifier * 3, type_section, offset)
961970
self.fields = []
962971

963972
for i in range(fields_len):
964973
# Each field struct is 3 pointers wide.
965974
addr = fields_addr + i * 3 * info.ptr_size
966975
if info.types <= addr < info.etypes:
967976
field_struct_offset = addr - info.types
968-
(field_name_ptr, field_type, field_offset) = struct.unpack_from(
977+
field_name_ptr, field_type, field_offset = struct.unpack_from(
969978
"<" + info.ptr_specifier * 3, type_section, field_struct_offset
970979
)
971980

@@ -1053,9 +1062,8 @@ def get_underlying_type(self, depth: int) -> str:
10531062
return "unsafe.Pointer"
10541063

10551064
def extract_at(self, info: ExtractInfo, addr: pointer, dereferenced_pointers: set[pointer], depth: int) -> GoData:
1056-
child_ptr = safe_read_unsigned(info, addr, info.ptr_size)
1057-
if child_ptr is not None:
1058-
return GoDataPointer(heuristic=Confidence.CERTAIN.to_float(), address=child_ptr)
1065+
if addr is not None:
1066+
return GoDataPointer(heuristic=Confidence.CERTAIN.to_float(), address=addr)
10591067
return GoDataBad(heuristic=Confidence.JUNK.to_float())
10601068

10611069

@@ -1376,7 +1384,7 @@ def parse(self, addr: pointer, nest_depth: int) -> GoData:
13761384
entries = None
13771385
break
13781386
# The next line is well-typed because if entries is None, then we already broke.
1379-
entries.extend(from_table) # type:ignore[union-attr]
1387+
entries.extend(from_table) # type: ignore[union-attr]
13801388

13811389
if entries is not None and len(entries) > 0:
13821390
# A valid map.

common/golang/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def go_find_func_name_offset(pc: int) -> tuple[str, int]:
108108
"""
109109
record = go_find_func(pc)
110110
if record is not None:
111-
(entry, gofunc) = record
111+
entry, gofunc = record
112112
return (gofunc.name, pc - entry)
113113

114114
# otherwise, gracefully fail for display purposes

0 commit comments

Comments
 (0)