Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/filesystem/cramfs.py: 100%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

36 statements  

1import binascii 

2import io 

3import struct 

4 

5from unblob.extractors import Command 

6 

7from ...file_utils import get_endian, iterate_file 

8from ...models import ( 

9 File, 

10 HandlerDoc, 

11 HandlerType, 

12 HexString, 

13 Reference, 

14 StructHandler, 

15 ValidChunk, 

16) 

17 

18CRAMFS_FLAG_FSID_VERSION_2 = 0x00000001 

19BIG_ENDIAN_MAGIC = 0x28_CD_3D_45 

20FSID_CRC_OFFSET = 32 

21FSID_CRC_SIZE = 4 

22 

23 

24def swap_int32(i): 

25 return struct.unpack("<I", struct.pack(">I", i))[0] 

26 

27 

28class CramFSHandler(StructHandler): 

29 NAME = "cramfs" 

30 

31 PATTERNS = [ 

32 HexString("28 CD 3D 45"), # big endian 

33 HexString("45 3D CD 28"), # little endian 

34 ] 

35 

36 C_DEFINITIONS = r""" 

37 typedef struct cramfs_header { 

38 uint32 magic; 

39 uint32 fs_size; 

40 uint32 flags; 

41 uint32 future; 

42 char signature[16]; 

43 uint32 fsid_crc; 

44 uint32 fsid_edition; 

45 uint32 fsid_blocks; 

46 uint32 fsid_files; 

47 char name[16]; 

48 } cramfs_header_t; 

49 """ 

50 HEADER_STRUCT = "cramfs_header_t" 

51 

52 EXTRACTOR = Command("7z", "x", "-y", "{inpath}", "-o{outdir}") 

53 

54 DOC = HandlerDoc( 

55 name="CramFS", 

56 description="CramFS is a lightweight, read-only file system format designed for simplicity and efficiency in embedded systems. It uses zlib compression for file data and stores metadata in a compact, contiguous structure.", 

57 handler_type=HandlerType.FILESYSTEM, 

58 vendor=None, 

59 references=[ 

60 Reference( 

61 title="CramFS Documentation", 

62 url="https://web.archive.org/web/20160304053532/http://sourceforge.net/projects/cramfs/", 

63 ), 

64 Reference( 

65 title="CramFS Wikipedia", 

66 url="https://en.wikipedia.org/wiki/Cramfs", 

67 ), 

68 ], 

69 limitations=[], 

70 ) 

71 

72 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

73 endian = get_endian(file, BIG_ENDIAN_MAGIC) 

74 header = self.parse_header(file, endian) 

75 valid_signature = header.signature == b"Compressed ROMFS" 

76 

77 if valid_signature and self._is_crc_valid(file, start_offset, header): 

78 return ValidChunk( 

79 start_offset=start_offset, 

80 end_offset=start_offset + header.fs_size, 

81 ) 

82 return None 

83 

84 def _is_crc_valid( 

85 self, 

86 file: File, 

87 start_offset: int, 

88 header, 

89 ) -> bool: 

90 # old cramfs format do not support crc 

91 if not (header.flags & CRAMFS_FLAG_FSID_VERSION_2): 

92 return True 

93 

94 file.seek(start_offset, io.SEEK_SET) 

95 header_bytes = bytearray(file.read(FSID_CRC_OFFSET + FSID_CRC_SIZE)) 

96 header_bytes[FSID_CRC_OFFSET : FSID_CRC_OFFSET + FSID_CRC_SIZE] = ( 

97 b"\x00\x00\x00\x00" 

98 ) 

99 computed_crc = binascii.crc32(header_bytes) 

100 

101 for chunk in iterate_file( 

102 file, 

103 start_offset + FSID_CRC_OFFSET + FSID_CRC_SIZE, 

104 header.fs_size - (FSID_CRC_OFFSET + FSID_CRC_SIZE), 

105 ): 

106 computed_crc = binascii.crc32(chunk, computed_crc) 

107 

108 # some vendors like their CRC's swapped, don't ask why 

109 return header.fsid_crc == computed_crc or header.fsid_crc == swap_int32( 

110 computed_crc 

111 )