From bd70270e1140eebb445797586b2f4fba8172708e Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 14 Apr 2025 16:26:21 -0500 Subject: [PATCH] scripts: Added -w/--word-bits to bound dbgleb128/dbgle32 parsing This is limited to dbgle32.py, dbgleb128.py, and dbgtag.py for now. This more closely matches how littlefs behaves, in that we read a bounded number of bytes before leb128 decoding. This minimizes bugs related to leb128 overflow and avoids reading inherently undecodable data. The previous unbounded behavior is still available with -w0. Note this gives dbgle32.py much more flexibility in that it can now decode other integer widths. Uh, ignore the name for now. At least it's self documenting that the default is 32-bits... --- Also fixed a bug in fromleb128 where size was reported incorrectly on offset + truncated leb128. --- scripts/dbgbmap.py | 2 +- scripts/dbgbmapd3.py | 2 +- scripts/dbgbtree.py | 2 +- scripts/dbgle32.py | 37 ++++++++++++++++++++++++++++--------- scripts/dbgleb128.py | 34 ++++++++++++++++++++++++++++------ scripts/dbglfs.py | 2 +- scripts/dbgmtree.py | 2 +- scripts/dbgrbyd.py | 2 +- scripts/dbgtag.py | 36 +++++++++++++++++++++++++++++------- 9 files changed, 91 insertions(+), 28 deletions(-) diff --git a/scripts/dbgbmap.py b/scripts/dbgbmap.py index 6bc07319..13a2c546 100755 --- a/scripts/dbgbmap.py +++ b/scripts/dbgbmap.py @@ -213,7 +213,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgbmapd3.py b/scripts/dbgbmapd3.py index e505d994..fdc1288a 100755 --- a/scripts/dbgbmapd3.py +++ b/scripts/dbgbmapd3.py @@ -243,7 +243,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 3854be01..a0aa088f 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -151,7 +151,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgle32.py b/scripts/dbgle32.py index 6c2a5d6c..4ceacd31 100755 --- a/scripts/dbgle32.py +++ b/scripts/dbgle32.py @@ -5,6 +5,7 @@ if __name__ == "__main__": __import__('sys').path.pop(0) import io +import math as mt import os import struct import sys @@ -21,18 +22,27 @@ def openio(path, mode='r', buffering=-1): else: return open(path, mode, buffering) -def fromle32(data, j=0): - return struct.unpack(' lines = [] j = 0 while j < len(data): - word = fromle32(data, j) + word = 0 + d = 0 + while (j+d < len(data) + and (d < n if word_bits != 0 else True)): + word |= data[j+d] << d + d += 1 + lines.append(( - ' '.join('%02x' % b for b in data[j:j+4]), + ' '.join('%02x' % b for b in data[j:j+d]), word)) - j += 4 + j += d # figure out widths w = [0] @@ -47,18 +57,21 @@ def dbg_le32s(data): def main(le32s, *, hex=False, - input=None): + input=None, + word_bits=32): hex_ = hex; del hex # interpret as a sequence of hex bytes if hex_: bytes_ = [b for le32 in le32s for b in le32.split()] - dbg_le32s(bytes(int(b, 16) for b in bytes_)) + dbg_le32s(bytes(int(b, 16) for b in bytes_), + word_bits=word_bits) # parse le32s in a file elif input: with openio(input, 'rb') as f: - dbg_le32s(f.read()) + dbg_le32s(f.read(), + word_bits=word_bits) # we don't currently have a default interpretation else: @@ -84,6 +97,12 @@ if __name__ == "__main__": parser.add_argument( '-i', '--input', help="Read le32s from this file. Can use - for stdin.") + parser.add_argument( + '-w', '--word-bits', + nargs='?', + type=lambda x: int(x, 0), + const=0, + help="Word size in bits. 0 is unbounded. Defaults to 32.") sys.exit(main(**{k: v for k, v in vars(parser.parse_intermixed_args()).items() if v is not None})) diff --git a/scripts/dbgleb128.py b/scripts/dbgleb128.py index 4d4ad8be..6652830b 100755 --- a/scripts/dbgleb128.py +++ b/scripts/dbgleb128.py @@ -5,6 +5,7 @@ if __name__ == "__main__": __import__('sys').path.pop(0) import io +import math as mt import os import struct import sys @@ -31,13 +32,25 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d -def dbg_leb128s(data): +def dbg_leb128s(data, *, + word_bits=32): + # figure out leb128 size in bytes + if word_bits != 0: + n = mt.ceil(word_bits / 7) + + # parse leb128s lines = [] j = 0 while j < len(data): - word, d = fromleb128(data, j) + # bounded leb128s? + if word_bits != 0: + word, d = fromleb128(data[j:j+n]) + # unbounded? + else: + word, d = fromleb128(data, j) + lines.append(( ' '.join('%02x' % b for b in data[j:j+d]), word)) @@ -56,18 +69,21 @@ def dbg_leb128s(data): def main(leb128s, *, hex=False, - input=None): + input=None, + word_bits=32): hex_ = hex; del hex # interpret as a sequence of hex bytes if hex_: bytes_ = [b for leb128 in leb128s for b in leb128.split()] - dbg_leb128s(bytes(int(b, 16) for b in bytes_)) + dbg_leb128s(bytes(int(b, 16) for b in bytes_), + word_bits=word_bits) # parse leb128s in a file elif input: with openio(input, 'rb') as f: - dbg_leb128s(f.read()) + dbg_leb128s(f.read(), + word_bits=word_bits) # we don't currently have a default interpretation else: @@ -93,6 +109,12 @@ if __name__ == "__main__": parser.add_argument( '-i', '--input', help="Read leb128s from this file. Can use - for stdin.") + parser.add_argument( + '-w', '--word-bits', + nargs='?', + type=lambda x: int(x, 0), + const=0, + help="Word size in bits. 0 is unbounded. Defaults to 32.") sys.exit(main(**{k: v for k, v in vars(parser.parse_intermixed_args()).items() if v is not None})) diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index 84dadc86..13fc5d85 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -170,7 +170,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index 1e5bc9cc..0fb53070 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -151,7 +151,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index a919cd02..889f31e2 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -161,7 +161,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 diff --git a/scripts/dbgtag.py b/scripts/dbgtag.py index d2b1bbe7..48584df4 100755 --- a/scripts/dbgtag.py +++ b/scripts/dbgtag.py @@ -5,6 +5,7 @@ if __name__ == "__main__": __import__('sys').path.pop(0) import io +import math as mt import os import struct import sys @@ -74,7 +75,7 @@ def fromleb128(data, j=0): if not b & 0x80: return word, d+1 d += 1 - return word, len(data) + return word, d def fromtag(data, j=0): d = 0 @@ -234,7 +235,12 @@ def list_tags(): w[0], 'LFSR_'+n, c)) -def dbg_tags(data): +def dbg_tags(data, *, + word_bits=32): + # figure out tag size in bytes + if word_bits != 0: + n = 2 + 2*mt.ceil(word_bits / 7) + lines = [] # interpret as ints? if not isinstance(data, bytes): @@ -247,7 +253,13 @@ def dbg_tags(data): else: j = 0 while j < len(data): - v, tag, w, size, d = fromtag(data, j) + # bounded tags? + if word_bits != 0: + v, tag, w, size, d = fromtag(data[j:j+n]) + # unbounded? + else: + v, tag, w, size, d = fromtag(data, j) + lines.append(( ' '.join('%02x' % b for b in data[j:j+d]), tagrepr(tag, w, size))) @@ -271,7 +283,8 @@ def dbg_tags(data): def main(tags, *, list=False, hex=False, - input=None): + input=None, + word_bits=32): list_ = list; del list hex_ = hex; del hex @@ -282,16 +295,19 @@ def main(tags, *, # interpret as a sequence of hex bytes elif hex_: bytes_ = [b for tag in tags for b in tag.split()] - dbg_tags(bytes(int(b, 16) for b in bytes_)) + dbg_tags(bytes(int(b, 16) for b in bytes_), + word_bits=word_bits) # parse tags in a file elif input: with openio(input, 'rb') as f: - dbg_tags(f.read()) + dbg_tags(f.read(), + word_bits=word_bits) # default to interpreting as ints else: - dbg_tags(int(tag, 0) for tag in tags) + dbg_tags((int(tag, 0) for tag in tags), + word_bits=word_bits) if __name__ == "__main__": @@ -315,6 +331,12 @@ if __name__ == "__main__": parser.add_argument( '-i', '--input', help="Read tags from this file. Can use - for stdin.") + parser.add_argument( + '-w', '--word-bits', + nargs='?', + type=lambda x: int(x, 0), + const=0, + help="Word size in bits. 0 is unbounded. Defaults to 32.") sys.exit(main(**{k: v for k, v in vars(parser.parse_intermixed_args()).items() if v is not None}))