#!/usr/bin/env python3 # -*- coding: UTF-8 -*- # Copyright (c) 2016 Andrei Tatar # Copyright (c) 2017-2018 Vrije Universiteit Amsterdam # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Module providing utilities for working with profile output and fliptables.""" import re import pprint as pp from collections import namedtuple from hammertime.dramtrans import DRAMAddr RE_DICT = re.compile("(?P[-\d\w]+)\s*:\s*(?P[-.\d\w]+)") #ADDR_RE = re.compile("r(?P\d+)\.bk(?P\d+)(\.col(?P\d+))?(\.p(?P\d+))?") ADDR_RE = re.compile("r(?P\d+)\.bk(?P\d+)(\.col(?P\d+))?") #ADDR_FMT = r'\((\w+)\s+(\w+)\s*(\w+)?\)' #ADDR_RE = re.compile(ADDR_FMT) # 87,c7,r16447.bk18.col3994 BFLIP_FMT = r'(?P\w{2}),(?P\w{2}),(?P[.\w\d]+)' BFLIP_RE = re.compile(BFLIP_FMT) ADDR_TRG_RE = re.compile("b(?P[\d\w]+)\.r(?P[\d\w]+)") #VICT_FMT = r'{} (?:{}\s?)+'.format(ADDR_FMT, BFLIP_FMT) #VICT_RE = re.compile(VICT_FMT) class Corruption(namedtuple('Corruption', ['addr', 'got', 'exp'])): def __str__(self): return '({0.addr}|{0.got:02x}|{0.exp:02x})'.format(self) def __repr__(self): return self.__str__() def to_flips(self): flips = [] pup = ~self.exp & self.got & 0xff pdn = self.exp & ~self.got & 0xff bit = 0 while pup: mask = 1 << bit if (pup & mask): flips.append(Flip(self.addr, bit, True)) pup &= ~mask bit += 1 bit = 0 while pdn: mask = 1 << bit if (pdn & mask): flips.append(Flip(self.addr, bit, False)) pdn &= ~mask bit += 1 return flips class Flip(namedtuple('Flip', ['addr', 'bit', 'pullup'])): def to_corruption(self, og=None): fmask = 1 << (self.bit % 8) if og is None: pat = 0 if self.pullup else 0xff else: pat = og val = pat | fmask if self.pullup else pat & ~fmask return Corruption(addr=self.addr, got=val, exp=pat) def to_physmem(self, msys): return type(self)(msys.resolve_reverse(self.addr), self.bit, self.pullup) Diff = namedtuple('Diff', ['self_only', 'common', 'other_only']) class Attack(namedtuple('Attack', ['targets', 'flips'])): def diff(self, other): if not isinstance(other, type(self)): raise TypeError('Attack instance expected for diff') elif not self.targets == other.targets: raise ValueError('Cannot diff attacks with different targets') else: return Diff( type(self)(self.targets, self.flips - other.flips), type(self)(self.targets, self.flips & other.flips), type(self)(self.targets, other.flips - self.flips) ) def merge(self, other): if not isinstance(other, type(self)): raise TypeError('Attack instance expected for merge') elif not self.targets == other.targets: raise ValueError('Cannot merge attacks with different targets') else: return type(self)(self.targets, self.flips | other.flips) def to_corruptions(self, pat=None): return ((x.addr, x.to_corruption(pat)) for x in self) def to_physmem(self, msys): return type(self)( targets=tuple(msys.resolve_reverse(x) for x in self.targets), flips={x.to_physmem(msys) for x in self.flips} ) def __iter__(self): return iter(sorted(self.flips)) @classmethod def __is_flip_far(cls, targets, victim): close = False for t in targets: if abs(t.row - victim.row) == 1: close = True if not close: print("Found flip far away from target row") print(targets) print(victim) @classmethod def decode_line(cls, line, msys=None): def parse_addr(s): res = ADDR_RE.match(s) if res == None: raise Exception("Couldn't parse") return DRAMAddr(**({k:int(v) for (k,v) in res.groupdict().items() if v != None})) def parse_addr_trg(s): res = ADDR_TRG_RE.match(s) if res == None: raise Exception("Couldn't parse") return DRAMAddr(**({k:int(v) for (k,v) in res.groupdict().items() if v != None})) targ, vict, *o = line.split(':') flips = set() targets = [parse_addr(s) for s in line.split('/')] for x in BFLIP_RE.finditer(vict): dct = x.groupdict() dct['addr'] = parse_addr(dct['addr']) dct['got'] = int(dct['got'], 16) dct['exp'] = int(dct['exp'], 16) cls.__is_flip_far(targets, dct['addr']) flips.update(Corruption(**(dct)).to_flips()) # vaddr = DRAMAddr(*(int(v, 16) for v in x.group(*range(1,4)) if v is not None)) # vcorr = [Corruption(*(int(v, 16) for v in y.groups())) for y in BFLIP_RE.finditer(x.group(0))] # for corr in vcorr: # flips.update(corr.to_flips(vaddr, msys)) return cls( targets= targets, flips=flips ) def encode(self, patterns=None): if patterns is None: patterns = [[0xff], [0]] corrs = [ [x for x in self.to_corruptions(pat) if x[1].exp != x[1].got] for pat in patterns ] tstr = ' '.join(str(x) for x in self.targets) + ' : ' return '\n'.join( tstr + ' '.join(' '.join(str(x) for x in c) for c in corr) for corr in corrs ) def decode_lines(lineiter): curatk = None for line in lineiter: atk = Attack.decode_line(line) if curatk is None: curatk = atk else: try: curatk = curatk.merge(atk) except ValueError: yield curatk curatk = atk if curatk is not None: yield curatk class Parameters(namedtuple("Parameters", ['h_cfg', 'd_cfg', 'h_rows', 'h_rounds', 'd_base'])): """""" @classmethod def parse_params(cls, string): matches = RE_DICT.findall(string) res = {} for m in matches: res[m[0]] = m[1] return cls(*res.values()) class Fliptable: def __init__(self, attacks): self.attacks=attacks def __eq__(self, other): if isinstance(other, type(self)): return self.attacks == other.attacks else: return NotImplemented def __len__(self): return len(self.attacks) def __iter__(self): return iter(self.attacks) def __str__(self): return '\n'.join(atk.encode() for atk in self) def __add__(self, other): return Fliptable(self.attacks + other.attacks) def diff(self, other): if not isinstance(other, type(self)): raise ValueError('Fliptable required for diff') uself = [] uother = [] common = [] satks = iter(self) oatks = iter(other) sa = next(satks, None) oa = next(oatks, None) while sa is not None or oa is not None: # Degenerate cases if sa is None: uother.append(oa) uother.extend(oatks) break if oa is None: uself.append(sa) uself.extend(satks) break try: adiff = sa.diff(oa) if adiff.self_only.flips: uself.append(adiff.self_only) common.append(adiff.common) if adiff.other_only.flips: uother.append(adiff.other_only) sa = next(satks, None) oa = next(oatks, None) except ValueError: if sa.targets < oa.targets: uself.append(sa) sa = next(satks, None) elif sa.targets > oa.targets: uother.append(oa) oa = next(oatks, None) FT = type(self) return Diff(FT(uself), FT(common), FT(uother)) def to_physmem(self, msys): return type(self)([x.to_physmem(msys) for x in self]) @classmethod def load_file(cls, fname): with open(fname, 'r') as f: # params_line = f.readline().remove("#") # read the first line which contains the parameters of the flip table # params = Parameters.parse_params(params_line) # flips = pd.read_csv(f) # attacks = attacks_generator() return cls(list(decode_lines(f))) def save_file(self, fname): with open(fname, 'w') as f: f.write(str(self))