cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

vmstate-static-checker.py (16117B)


      1#!/usr/bin/env python3
      2#
      3# Compares vmstate information stored in JSON format, obtained from
      4# the -dump-vmstate QEMU command.
      5#
      6# Copyright 2014 Amit Shah <amit.shah@redhat.com>
      7# Copyright 2014 Red Hat, Inc.
      8#
      9# This program is free software; you can redistribute it and/or modify
     10# it under the terms of the GNU General Public License as published by
     11# the Free Software Foundation; either version 2 of the License, or
     12# (at your option) any later version.
     13#
     14# This program is distributed in the hope that it will be useful,
     15# but WITHOUT ANY WARRANTY; without even the implied warranty of
     16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17# GNU General Public License for more details.
     18#
     19# You should have received a copy of the GNU General Public License along
     20# with this program; if not, see <http://www.gnu.org/licenses/>.
     21
     22import argparse
     23import json
     24import sys
     25
     26# Count the number of errors found
     27taint = 0
     28
     29def bump_taint():
     30    global taint
     31
     32    # Ensure we don't wrap around or reset to 0 -- the shell only has
     33    # an 8-bit return value.
     34    if taint < 255:
     35        taint = taint + 1
     36
     37
     38def check_fields_match(name, s_field, d_field):
     39    if s_field == d_field:
     40        return True
     41
     42    # Some fields changed names between qemu versions.  This list
     43    # is used to whitelist such changes in each section / description.
     44    changed_names = {
     45        'apic': ['timer', 'timer_expiry'],
     46        'e1000': ['dev', 'parent_obj'],
     47        'ehci': ['dev', 'pcidev'],
     48        'I440FX': ['dev', 'parent_obj'],
     49        'ich9_ahci': ['card', 'parent_obj'],
     50        'ich9-ahci': ['ahci', 'ich9_ahci'],
     51        'ioh3420': ['PCIDevice', 'PCIEDevice'],
     52        'ioh-3240-express-root-port': ['port.br.dev',
     53                                       'parent_obj.parent_obj.parent_obj',
     54                                       'port.br.dev.exp.aer_log',
     55                                'parent_obj.parent_obj.parent_obj.exp.aer_log'],
     56        'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
     57                       'hw_cursor_y', 'vga.hw_cursor_y'],
     58        'lsiscsi': ['dev', 'parent_obj'],
     59        'mch': ['d', 'parent_obj'],
     60        'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
     61        'pcnet': ['pci_dev', 'parent_obj'],
     62        'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
     63        'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
     64                     'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
     65                     'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
     66                     'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
     67                     'tmr.timer', 'ar.tmr.timer',
     68                     'tmr.overflow_time', 'ar.tmr.overflow_time',
     69                     'gpe', 'ar.gpe'],
     70        'rtl8139': ['dev', 'parent_obj'],
     71        'qxl': ['num_surfaces', 'ssd.num_surfaces'],
     72        'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
     73        'usb-host': ['dev', 'parent_obj'],
     74        'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
     75        'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
     76        'vmware_vga': ['card', 'parent_obj'],
     77        'vmware_vga_internal': ['depth', 'new_depth'],
     78        'xhci': ['pci_dev', 'parent_obj'],
     79        'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
     80        'xio3130-express-downstream-port': ['port.br.dev',
     81                                            'parent_obj.parent_obj.parent_obj',
     82                                            'port.br.dev.exp.aer_log',
     83                                'parent_obj.parent_obj.parent_obj.exp.aer_log'],
     84        'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
     85        'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
     86                                          'br.dev.exp.aer_log',
     87                                          'parent_obj.parent_obj.exp.aer_log'],
     88        'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
     89                      'mem_win_addr', 'mig_mem_win_addr',
     90                      'mem_win_size', 'mig_mem_win_size',
     91                      'io_win_addr', 'mig_io_win_addr',
     92                      'io_win_size', 'mig_io_win_size'],
     93    }
     94
     95    if not name in changed_names:
     96        return False
     97
     98    if s_field in changed_names[name] and d_field in changed_names[name]:
     99        return True
    100
    101    return False
    102
    103def get_changed_sec_name(sec):
    104    # Section names can change -- see commit 292b1634 for an example.
    105    changes = {
    106        "ICH9 LPC": "ICH9-LPC",
    107        "e1000-82540em": "e1000",
    108    }
    109
    110    for item in changes:
    111        if item == sec:
    112            return changes[item]
    113        if changes[item] == sec:
    114            return item
    115    return ""
    116
    117def exists_in_substruct(fields, item):
    118    # Some QEMU versions moved a few fields inside a substruct.  This
    119    # kept the on-wire format the same.  This function checks if
    120    # something got shifted inside a substruct.  For example, the
    121    # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
    122
    123    if not "Description" in fields:
    124        return False
    125
    126    if not "Fields" in fields["Description"]:
    127        return False
    128
    129    substruct_fields = fields["Description"]["Fields"]
    130
    131    if substruct_fields == []:
    132        return False
    133
    134    return check_fields_match(fields["Description"]["name"],
    135                              substruct_fields[0]["field"], item)
    136
    137
    138def check_fields(src_fields, dest_fields, desc, sec):
    139    # This function checks for all the fields in a section.  If some
    140    # fields got embedded into a substruct, this function will also
    141    # attempt to check inside the substruct.
    142
    143    d_iter = iter(dest_fields)
    144    s_iter = iter(src_fields)
    145
    146    # Using these lists as stacks to store previous value of s_iter
    147    # and d_iter, so that when time comes to exit out of a substruct,
    148    # we can go back one level up and continue from where we left off.
    149
    150    s_iter_list = []
    151    d_iter_list = []
    152
    153    advance_src = True
    154    advance_dest = True
    155    unused_count = 0
    156
    157    while True:
    158        if advance_src:
    159            try:
    160                s_item = next(s_iter)
    161            except StopIteration:
    162                if s_iter_list == []:
    163                    break
    164
    165                s_iter = s_iter_list.pop()
    166                continue
    167        else:
    168            if unused_count == 0:
    169                # We want to avoid advancing just once -- when entering a
    170                # dest substruct, or when exiting one.
    171                advance_src = True
    172
    173        if advance_dest:
    174            try:
    175                d_item = next(d_iter)
    176            except StopIteration:
    177                if d_iter_list == []:
    178                    # We were not in a substruct
    179                    print("Section \"" + sec + "\",", end=' ')
    180                    print("Description " + "\"" + desc + "\":", end=' ')
    181                    print("expected field \"" + s_item["field"] + "\",", end=' ')
    182                    print("while dest has no further fields")
    183                    bump_taint()
    184                    break
    185
    186                d_iter = d_iter_list.pop()
    187                advance_src = False
    188                continue
    189        else:
    190            if unused_count == 0:
    191                advance_dest = True
    192
    193        if unused_count != 0:
    194            if advance_dest == False:
    195                unused_count = unused_count - s_item["size"]
    196                if unused_count == 0:
    197                    advance_dest = True
    198                    continue
    199                if unused_count < 0:
    200                    print("Section \"" + sec + "\",", end=' ')
    201                    print("Description \"" + desc + "\":", end=' ')
    202                    print("unused size mismatch near \"", end=' ')
    203                    print(s_item["field"] + "\"")
    204                    bump_taint()
    205                    break
    206                continue
    207
    208            if advance_src == False:
    209                unused_count = unused_count - d_item["size"]
    210                if unused_count == 0:
    211                    advance_src = True
    212                    continue
    213                if unused_count < 0:
    214                    print("Section \"" + sec + "\",", end=' ')
    215                    print("Description \"" + desc + "\":", end=' ')
    216                    print("unused size mismatch near \"", end=' ')
    217                    print(d_item["field"] + "\"")
    218                    bump_taint()
    219                    break
    220                continue
    221
    222        if not check_fields_match(desc, s_item["field"], d_item["field"]):
    223            # Some fields were put in substructs, keeping the
    224            # on-wire format the same, but breaking static tools
    225            # like this one.
    226
    227            # First, check if dest has a new substruct.
    228            if exists_in_substruct(d_item, s_item["field"]):
    229                # listiterators don't have a prev() function, so we
    230                # have to store our current location, descend into the
    231                # substruct, and ensure we come out as if nothing
    232                # happened when the substruct is over.
    233                #
    234                # Essentially we're opening the substructs that got
    235                # added which didn't change the wire format.
    236                d_iter_list.append(d_iter)
    237                substruct_fields = d_item["Description"]["Fields"]
    238                d_iter = iter(substruct_fields)
    239                advance_src = False
    240                continue
    241
    242            # Next, check if src has substruct that dest removed
    243            # (can happen in backward migration: 2.0 -> 1.5)
    244            if exists_in_substruct(s_item, d_item["field"]):
    245                s_iter_list.append(s_iter)
    246                substruct_fields = s_item["Description"]["Fields"]
    247                s_iter = iter(substruct_fields)
    248                advance_dest = False
    249                continue
    250
    251            if s_item["field"] == "unused" or d_item["field"] == "unused":
    252                if s_item["size"] == d_item["size"]:
    253                    continue
    254
    255                if d_item["field"] == "unused":
    256                    advance_dest = False
    257                    unused_count = d_item["size"] - s_item["size"]
    258                    continue
    259
    260                if s_item["field"] == "unused":
    261                    advance_src = False
    262                    unused_count = s_item["size"] - d_item["size"]
    263                    continue
    264
    265            print("Section \"" + sec + "\",", end=' ')
    266            print("Description \"" + desc + "\":", end=' ')
    267            print("expected field \"" + s_item["field"] + "\",", end=' ')
    268            print("got \"" + d_item["field"] + "\"; skipping rest")
    269            bump_taint()
    270            break
    271
    272        check_version(s_item, d_item, sec, desc)
    273
    274        if not "Description" in s_item:
    275            # Check size of this field only if it's not a VMSTRUCT entry
    276            check_size(s_item, d_item, sec, desc, s_item["field"])
    277
    278        check_description_in_list(s_item, d_item, sec, desc)
    279
    280
    281def check_subsections(src_sub, dest_sub, desc, sec):
    282    for s_item in src_sub:
    283        found = False
    284        for d_item in dest_sub:
    285            if s_item["name"] != d_item["name"]:
    286                continue
    287
    288            found = True
    289            check_descriptions(s_item, d_item, sec)
    290
    291        if not found:
    292            print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
    293            print("Subsection \"" + s_item["name"] + "\" not found")
    294            bump_taint()
    295
    296
    297def check_description_in_list(s_item, d_item, sec, desc):
    298    if not "Description" in s_item:
    299        return
    300
    301    if not "Description" in d_item:
    302        print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
    303        print("Field \"" + s_item["field"] + "\": missing description")
    304        bump_taint()
    305        return
    306
    307    check_descriptions(s_item["Description"], d_item["Description"], sec)
    308
    309
    310def check_descriptions(src_desc, dest_desc, sec):
    311    check_version(src_desc, dest_desc, sec, src_desc["name"])
    312
    313    if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
    314        print("Section \"" + sec + "\":", end=' ')
    315        print("Description \"" + src_desc["name"] + "\"", end=' ')
    316        print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
    317        bump_taint()
    318        return
    319
    320    for f in src_desc:
    321        if not f in dest_desc:
    322            print("Section \"" + sec + "\"", end=' ')
    323            print("Description \"" + src_desc["name"] + "\":", end=' ')
    324            print("Entry \"" + f + "\" missing")
    325            bump_taint()
    326            continue
    327
    328        if f == 'Fields':
    329            check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
    330
    331        if f == 'Subsections':
    332            check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
    333
    334
    335def check_version(s, d, sec, desc=None):
    336    if s["version_id"] > d["version_id"]:
    337        print("Section \"" + sec + "\"", end=' ')
    338        if desc:
    339            print("Description \"" + desc + "\":", end=' ')
    340        print("version error:", s["version_id"], ">", d["version_id"])
    341        bump_taint()
    342
    343    if not "minimum_version_id" in d:
    344        return
    345
    346    if s["version_id"] < d["minimum_version_id"]:
    347        print("Section \"" + sec + "\"", end=' ')
    348        if desc:
    349            print("Description \"" + desc + "\":", end=' ')
    350            print("minimum version error:", s["version_id"], "<", end=' ')
    351            print(d["minimum_version_id"])
    352            bump_taint()
    353
    354
    355def check_size(s, d, sec, desc=None, field=None):
    356    if s["size"] != d["size"]:
    357        print("Section \"" + sec + "\"", end=' ')
    358        if desc:
    359            print("Description \"" + desc + "\"", end=' ')
    360        if field:
    361            print("Field \"" + field + "\"", end=' ')
    362        print("size mismatch:", s["size"], ",", d["size"])
    363        bump_taint()
    364
    365
    366def check_machine_type(s, d):
    367    if s["Name"] != d["Name"]:
    368        print("Warning: checking incompatible machine types:", end=' ')
    369        print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
    370    return
    371
    372
    373def main():
    374    help_text = "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST.  Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs.  The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it.  Other parameters to QEMU do not matter, except the -M (machine type) parameter."
    375
    376    parser = argparse.ArgumentParser(description=help_text)
    377    parser.add_argument('-s', '--src', type=argparse.FileType('r'),
    378                        required=True,
    379                        help='json dump from src qemu')
    380    parser.add_argument('-d', '--dest', type=argparse.FileType('r'),
    381                        required=True,
    382                        help='json dump from dest qemu')
    383    parser.add_argument('--reverse', required=False, default=False,
    384                        action='store_true',
    385                        help='reverse the direction')
    386    args = parser.parse_args()
    387
    388    src_data = json.load(args.src)
    389    dest_data = json.load(args.dest)
    390    args.src.close()
    391    args.dest.close()
    392
    393    if args.reverse:
    394        temp = src_data
    395        src_data = dest_data
    396        dest_data = temp
    397
    398    for sec in src_data:
    399        dest_sec = sec
    400        if not dest_sec in dest_data:
    401            # Either the section name got changed, or the section
    402            # doesn't exist in dest.
    403            dest_sec = get_changed_sec_name(sec)
    404            if not dest_sec in dest_data:
    405                print("Section \"" + sec + "\" does not exist in dest")
    406                bump_taint()
    407                continue
    408
    409        s = src_data[sec]
    410        d = dest_data[dest_sec]
    411
    412        if sec == "vmschkmachine":
    413            check_machine_type(s, d)
    414            continue
    415
    416        check_version(s, d, sec)
    417
    418        for entry in s:
    419            if not entry in d:
    420                print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
    421                print("missing")
    422                bump_taint()
    423                continue
    424
    425            if entry == "Description":
    426                check_descriptions(s[entry], d[entry], sec)
    427
    428    return taint
    429
    430
    431if __name__ == '__main__':
    432    sys.exit(main())