@@ -650,6 +650,7 @@ class VMDKInspector(FileInspector):
650
650
# at 0x200 and 1MB - 1
651
651
DESC_OFFSET = 0x200
652
652
DESC_MAX_SIZE = (1 << 20 ) - 1
653
+ GD_AT_END = 0xffffffffffffffff
653
654
654
655
def __init__ (self , * a , ** k ):
655
656
super (VMDKInspector , self ).__init__ (* a , ** k )
@@ -662,15 +663,21 @@ def post_process(self):
662
663
if not self .region ('header' ).complete :
663
664
return
664
665
665
- sig , ver , _flags , _sectors , _grain , desc_sec , desc_num = struct .unpack (
666
- '<4sIIQQQQ' , self .region ('header' ).data [:44 ])
666
+ (sig , ver , _flags , _sectors , _grain , desc_sec , desc_num ,
667
+ _numGTEsperGT , _rgdOffset , gdOffset ) = struct .unpack (
668
+ '<4sIIQQQQIQQ' , self .region ('header' ).data [:64 ])
667
669
668
670
if sig != b'KDMV' :
669
671
raise ImageFormatError ('Signature KDMV not found: %r' % sig )
670
672
671
673
if ver not in (1 , 2 , 3 ):
672
674
raise ImageFormatError ('Unsupported format version %i' % ver )
673
675
676
+ if gdOffset == self .GD_AT_END :
677
+ # This means we have a footer, which takes precedence over the
678
+ # header, which we cannot support since we stream.
679
+ raise ImageFormatError ('Unsupported VMDK footer' )
680
+
674
681
# Since we parse both desc_sec and desc_num (the location of the
675
682
# VMDK's descriptor, expressed in 512 bytes sectors) we enforce a
676
683
# check on the bounds to create a reasonable CaptureRegion. This
@@ -718,6 +725,59 @@ def virtual_size(self):
718
725
719
726
return sectors * 512
720
727
728
+ def safety_check (self ):
729
+ if (not self .has_region ('descriptor' ) or
730
+ not self .region ('descriptor' ).complete ):
731
+ return False
732
+
733
+ try :
734
+ # Descriptor is padded to 512 bytes
735
+ desc_data = self .region ('descriptor' ).data .rstrip (b'\x00 ' )
736
+ # Descriptor is actually case-insensitive ASCII text
737
+ desc_text = desc_data .decode ('ascii' ).lower ()
738
+ except UnicodeDecodeError :
739
+ LOG .error ('VMDK descriptor failed to decode as ASCII' )
740
+ raise ImageFormatError ('Invalid VMDK descriptor data' )
741
+
742
+ extent_access = ('rw' , 'rdonly' , 'noaccess' )
743
+ header_fields = []
744
+ extents = []
745
+ ddb = []
746
+
747
+ # NOTE(danms): Cautiously parse the VMDK descriptor. Each line must
748
+ # be something we understand, otherwise we refuse it.
749
+ for line in [x .strip () for x in desc_text .split ('\n ' )]:
750
+ if line .startswith ('#' ) or not line :
751
+ # Blank or comment lines are ignored
752
+ continue
753
+ elif line .startswith ('ddb' ):
754
+ # DDB lines are allowed (but not used by us)
755
+ ddb .append (line )
756
+ elif '=' in line and ' ' not in line .split ('=' )[0 ]:
757
+ # Header fields are a single word followed by an '=' and some
758
+ # value
759
+ header_fields .append (line )
760
+ elif line .split (' ' )[0 ] in extent_access :
761
+ # Extent lines start with one of the three access modes
762
+ extents .append (line )
763
+ else :
764
+ # Anything else results in a rejection
765
+ LOG .error ('Unsupported line %r in VMDK descriptor' , line )
766
+ raise ImageFormatError ('Invalid VMDK descriptor data' )
767
+
768
+ # Check all the extent lines for concerning content
769
+ for extent_line in extents :
770
+ if '/' in extent_line :
771
+ LOG .error ('Extent line %r contains unsafe characters' ,
772
+ extent_line )
773
+ return False
774
+
775
+ if not extents :
776
+ LOG .error ('VMDK file specified no extents' )
777
+ return False
778
+
779
+ return True
780
+
721
781
def __str__ (self ):
722
782
return 'vmdk'
723
783
0 commit comments