BitFlip.py 13.8 KB
Newer Older
German Leon's avatar
German Leon committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import random
import sys

import gdb
import re
import common_functions as cf  # All common functions will be at common_functions module
import common_parameters as cp  # All common parameters will be at common_parameters module

# from collections import deque

"""
BitFlip class
to implement the bit flip process.
The methods in this class will execute only inside a CUDA kernel
"""


class BitFlip:
    def __init__(self, **kwargs):
        # If kernel is not accessible it must return
        self.__bits_to_flip = kwargs.get('bits_to_flip')
        self.__fault_model = kwargs.get('fault_model')
        self.__logging = kwargs.get('logging')
        self.__injection_site = kwargs.get('injection_site')
German Leon's avatar
German Leon committed
25
        self.__maxregs=int(kwargs.get('max_regs'))
German Leon's avatar
German Leon committed
26
27
28
29
30
31
32
33
34
35
36
        self.fault_injected = False

    """
    print exception info
    """

    @staticmethod
    def __exception_str():
        exc_type, exc_obj, exc_tb = sys.exc_info()
        return "Exception type {} at line {}".format(exc_type, exc_tb.tb_lineno)

37
38
39
40
    @staticmethod
    def numreg (kernel):
      try:
        disassemble_array = cf.execute_command(gdb=gdb, to_execute="disassemble {}".format(kernel))
German Leon's avatar
German Leon committed
41
        #print ("kenel={}".format(kernel))
42
43
44
45
46
47
48
      except:
        disassemble_array = cf.execute_command(gdb=gdb, to_execute="disassemble")
      listareg=set()
      listaregdst=set()
      listaregcond=set()
      for i in range(0, len(disassemble_array) - 1):
        line = disassemble_array[i]
German Leon's avatar
German Leon committed
49
        #m=re.match(r".*:\t(\S+) .*",line)#Para que es m
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
        todacadena="ASSM_LINE:{}".format(line)
        lista=re.findall(r"R(\d+)", line)
        if (len(lista) > 0):   	
           listareg.update(lista)
        lista=re.findall(r" P(\d+)", line)
        if (len(lista) > 0): 
           listaregcond.update(lista)

      print ("Registros Visibles ({})".format(len(listareg)))
      print (listareg)
      print ("Registros destino ({})".format(len(listaregdst)))
      print (listaregdst)
      print ("Registros condicionales ({})".format(len(listaregcond)))
      print (listaregcond)
      return (listareg)
German Leon's avatar
German Leon committed
65
66
67
    """
    TODO: Describe the method
    """
German Leon's avatar
German Leon committed
68
69
    def asmline(self):
      #Leo la linea de ejecucion
German Leon's avatar
German Leon committed
70
71
72
73
74
      linea=cf.execute_command(gdb=gdb, to_execute="x/1i $pc")
      self.__logging.info("ASSM_LINE:{}".format(linea[0]))
      return linea
      
      
German Leon's avatar
German Leon committed
75
76
77
    def reg_asmline( self):
      #Obtengo los registros de la instruccion
      linea= self.asmline()
German Leon's avatar
German Leon committed
78
79
      
     
German Leon's avatar
German Leon committed
80
      lista=re.findall(r"R(\d+)", linea[0])
German Leon's avatar
German Leon committed
81
82
83
      
      #Ahora son todos los registros.
      #lista=range(0,self.__maxregs)
German Leon's avatar
German Leon committed
84
85
      setlista=set()
      setlista.update(lista)
German Leon's avatar
German Leon committed
86
      
German Leon's avatar
German Leon committed
87
   
German Leon's avatar
German Leon committed
88
      return setlista
German Leon's avatar
German Leon committed
89
90
91
      
      
      
German Leon's avatar
German Leon committed
92
93
94
95
96
97
98
99
100
101
102
    def regmod (self):
      lista=self.reg_asmline()
      while len(lista) == 0: #Habria que poner un limite. 
        #Busco una instruccion que referiencia algun registro 
        self.__logging.info("INSTRUCTION WITHOUT DESTINATION REGISTER")              
      	gdb.execute("nexti")
        lista=self.reg_asmline()
      listareg=[" R{} ".format(x) for x in lista]
      strlistareg="info registers " 
      for x in listareg:
        strlistareg+=x;
German Leon's avatar
German Leon committed
103
      
German Leon's avatar
German Leon committed
104
      self.__stringregs=strlistareg
German Leon's avatar
German Leon committed
105

German Leon's avatar
German Leon committed
106
107
108
109
110
111
112
      #Obtengo el valor de los registro referenciados
      valores= cf.execute_command(gdb=gdb, to_execute=strlistareg)
      return valores
    def nextinstr(self):
       #Obtengo el valor de los registro referenciados por la primera instruccipn
       #self.__logging.info("===============================")
       gdb.execute("nexti")
German Leon's avatar
German Leon committed
113
      
German Leon's avatar
German Leon committed
114
       linea= self.asmline()
German Leon's avatar
German Leon committed
115
     
German Leon's avatar
German Leon committed
116
117
       valores= cf.execute_command(gdb=gdb, to_execute=self.__stringregs)  
       return valores
German Leon's avatar
German Leon committed
118
       
German Leon's avatar
German Leon committed
119
120
121
122
123
    def dictreg(self,valores):
      #self.__logging.info("Execute dictreg")
      #Almaceno en un dictionario los valores de los registros
      regs={}
      for x in valores:
German Leon's avatar
German Leon committed
124
        #print(x)
German Leon's avatar
German Leon committed
125
126
127
        m = re.match(r".*R(\d+).*0x([0-9a-fA-F]+).*", x)
        if m:
          regs[m.group(1)]=m.group(2)
German Leon's avatar
German Leon committed
128
          
German Leon's avatar
German Leon committed
129
130
      return regs    
    def cmpregdst (self,valores,regs):
German Leon's avatar
German Leon committed
131
      
German Leon's avatar
German Leon committed
132
133
134
135
136
137
138
139
140
141
142
143
144
      regdst=set()
     
      
      for x in valores:
        m = re.match(r".*R(\d+).*0x([0-9a-fA-F]+).*", x)
        if m:
          #print("El registro {} tiene {} y tenia{}".format(m.group(1),m.group(2),regs[m.group(1)]))
          if (regs[m.group(1)]!=m.group(2)):
            #print("Diferente")
            regdst.add(m.group(1))  
      #self.__logging.info(str(len(regdst)))
      return regdst
    def  __inst_generic_injector(self):
German Leon's avatar
German Leon committed
145
  
German Leon's avatar
German Leon committed
146
        regs=self.dictreg(self.regmod())
German Leon's avatar
German Leon committed
147
       
German Leon's avatar
German Leon committed
148
        valores=self.nextinstr()
German Leon's avatar
German Leon committed
149
      
German Leon's avatar
German Leon committed
150
        r=self.cmpregdst(valores,regs)
German Leon's avatar
German Leon committed
151
       
German Leon's avatar
German Leon committed
152
153
154
155
156
157
158
159
160
        while (len(r) ==0):
             self.__logging.info("INSTRUCTION WITHOUT OPERANDS")
             gdb.execute("nexti")
             regs=self.dictreg( self.regmod())
             valores=self.nextinstr()
             r=self.cmpregdst(valores,regs)
                    
        self.__register="R{}".format(r.pop())
        self.__logging.info("SELECTED_REGISTER:{}".format(self.__register))
German Leon's avatar
German Leon committed
161
        
German Leon's avatar
German Leon committed
162
163
164
165
        
                # __rf_generic_injector will set fault_injected attribute
        self.__rf_generic_injector()
     
German Leon's avatar
German Leon committed
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
    def single_event(self):
        # fault_injected attribute will be set by the methods that perform fault injection
        # Focusing the thread. If focus succeed, otherwise not
        if self.__thread_focus():
            # Do the fault injection magic

            # Register File mode
            if cp.RF == self.__injection_site:
                # Select the register before the injection
                self.__select_register()
                # RF is the default mode of injection
                self.__rf_generic_injector()

            # Instruction Output mode
            elif cp.INST_OUT == self.__injection_site:
                self.__inst_generic_injector()

            # Instruction Address mode
            elif cp.INST_ADD == self.__injection_site:
                self.__logging.exception("INST_ADD NOT IMPLEMENTED YET")
                self.__logging.exception(self.__exception_str())
                self.fault_injected = False

            # Test fault injection result
            self.__logging.info("Fault Injection " + ("Successful" if self.fault_injected else "Went Wrong"))

    """
    Selects a valid thread for a specific
    kernel
    return the coordinates for the block
    and the thread
    """

    def __thread_focus(self):
        try:
            # Selecting the block, it must be a valid block
            blocks = cf.execute_command(gdb=gdb, to_execute="info cuda blocks")
            # empty lists are always false
            while blocks:
                chosen_block = random.choice(blocks)
                # remove it from the options
                blocks.remove(chosen_block)

                m = re.match(r".*\(.*\).*\((\d+),(\d+),(\d+)\).*", chosen_block)
                # Try to focus
                if m:
                    change_focus_block_cmd = "cuda block {},{},{}".format(m.group(1), m.group(2), m.group(3))
                    block_focus = cf.execute_command(gdb=gdb, to_execute=change_focus_block_cmd)
                    # empty lists are always false
                    if block_focus:
                        # Thread focus return information
                        self.__logging.info("CUDA_BLOCK_FOCUS:{}".format(block_focus))
                        # No need to continue to seek
                        break

            # Selecting the thread
            threads = cf.execute_command(gdb=gdb, to_execute="info cuda threads")
            while threads:
                chosen_thread = random.choice(threads)
                # remove it from the options
                threads.remove(chosen_thread)

                m = re.match(r".*\(.*\).*\(.*\).*\(.*\).*\((\d+),(\d+),(\d+)\).*", chosen_thread)
                # Try to focus
                if m:
                    change_focus_thread_cmd = "cuda thread {},{},{}".format(m.group(1), m.group(2), m.group(3))
                    thread_focus = cf.execute_command(gdb=gdb, to_execute=change_focus_thread_cmd)

                    # empty lists are always false
                    if thread_focus:
                        # Thread focus return information
                        self.__logging.info("CUDA_THREAD_FOCUS:{}".format(thread_focus))
                        # No need to continue to seek
                        break

        except Exception as err:
            err_str = str(err)
            self.__logging.exception("CUDA_FOCUS_CANNOT_BE_REQUESTED, ERROR:" + err_str)

            # No need to continue if no active kernel
            if err_str == cp.FOCUS_ERROR_STRING:
                return False

        # If we are inside the kernel return true
        return True

    """
    Flip a bit or multiple bits based on a fault model
    """

    def __rf_generic_injector(self):
        try:
            # get register content
German Leon's avatar
German Leon committed
259
            self.asmline()
German Leon's avatar
German Leon committed
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
            reg_cmd = cf.execute_command(gdb, "p/t ${}".format(self.__register))
            m = re.match(r'\$(\d+)[ ]*=[ ]*(\S+).*', reg_cmd[0])

            reg_content_old = str(m.group(2))
            # Make sure that binary value will have max size register
            reg_content_full_bits = str(
                '0' * (cp.SINGLE_MAX_SIZE_REGISTER - len(reg_content_old))) + reg_content_old

            reg_content_new = ''

            # Single or double bit flip or Least significant bits
            if self.__fault_model in [cp.FLIP_SINGLE_BIT, cp.FLIP_TWO_BITS, cp.LEAST_16_BITS, cp.LEAST_8_BITS]:
                # single bit flip or Double bit flip
                reg_content_new = reg_content_full_bits
                for bit_to_flip in self.__bits_to_flip:
                    reg_content_new = self.__flip_a_bit(int(bit_to_flip), reg_content_new)
                reg_content_new = hex(int(reg_content_new, 2))

            # Random value or Zero value
            elif self.__fault_model == cp.RANDOM_VALUE or self.__fault_model == cp.ZERO_VALUE:
                # random value is stored at bits_to_flip[0]
                reg_content_new = self.__bits_to_flip[0]

            # send the new value to gdb
            flip_command = "set ${} = {}".format(self.__register, reg_content_new)
            reg_cmd_flipped = cf.execute_command(gdb, flip_command)

            # ['$2 = 100000000111111111111111']
            modify_output = cf.execute_command(gdb, "p/t ${}".format(self.__register))[0]

            # With string split it is easier to crash
            reg_modified = re.match(r"(.*)=(.*)", modify_output).group(2).strip()

            # Return the fault confirmation
            self.fault_injected = reg_content_old != reg_modified

            # Log the register only if the fault was injected, reduce unnecessary file write
            if self.fault_injected:
                # LOGGING
                self.__logging.info("SELECTED_REGISTER:{}".format(self.__register))
                # Logging info result extracted from register
                self.__logging.info("old_value:{}".format(reg_content_old))
                # Also logging the new value
                self.__logging.info("new_value:{}".format(reg_modified))

                # Log if something was printed
                # empty list is always false
                if reg_cmd_flipped:
                    self.__logging.info("flip command return:{}".format(reg_cmd_flipped))

        except Exception as err:
            self.__logging.exception("fault_injection_python_exception: {}".format(err))
            self.__logging.exception(self.__exception_str())
            self.fault_injected = False

    """
    Flip only a bit in a register content
    """

    @staticmethod
    def __flip_a_bit(bit_to_flip, reg_content):
        return reg_content[:bit_to_flip] + ('0' if reg_content[bit_to_flip] == '1' else '1') + reg_content[
                                                                                               bit_to_flip + 1:]

    """
    Runtime select register
    """

    def __select_register(self):
        # Start on 1 to consider R0
leon@uji.es's avatar
leon@uji.es committed
330
        #max_num_register = 
German Leon's avatar
German Leon committed
331
        # registers_list.popleft()
leon@uji.es's avatar
leon@uji.es committed
332
333
334
335
        #registers_list = cf.execute_command(gdb=gdb, to_execute="info registers")
        #for line in registers_list:
        #    m = re.match(r".*R.*0x([0-9a-fA-F]+).*", line)
        #    if m and m.group(1) != '0':
German Leon's avatar
German Leon committed
336
                #self.__logging.info ("LIne salida:{}".format(m.group(1))
leon@uji.es's avatar
leon@uji.es committed
337
        #         max_num_register += 1
German Leon's avatar
German Leon committed
338
                #self.__logging.info("LIne entrada {}--max{}".format(line,max_num_register))
German Leon's avatar
German Leon committed
339
340
341
        #self.__logging.info("MAX_NUM_REGISTER:{}".format(self.__maxregs))        
        self.__register = "R{}".format(random.randint(0, (self.__maxregs)-1))
       
German Leon's avatar
German Leon committed
342
343
344
345
    """
    Instruction injector    
    """

German Leon's avatar
German Leon committed
346
    def __inst_generic_injector_old(self):
German Leon's avatar
German Leon committed
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
        disassemble_array = cf.execute_command(gdb=gdb, to_execute="disassemble")
        # Search the line to inject
        # -1 will use the next instruction after program counter
        for i in range(0, len(disassemble_array) - 1):
            next_line = disassemble_array[i + 1]

            # => defines where the program counter is
            # There is an instruction on this line
            # then inject in the output register
            if "=>" in disassemble_array[i] and re.match(r".*:\t(\S+) .*", next_line):
                # If it gets the PC + 1 then I must inject in the input of the next instruction
                # Which is the most right register (-1 index)
                self.__register = "R{}".format(re.findall(r"R(\d+)", next_line)[-1])
                self.__logging.info("SELECTED_REGISTER:{}".format(self.__register))
                self.__logging.info("ASSM_LINE:{}".format(next_line))

                # __rf_generic_injector will set fault_injected attribute
                self.__rf_generic_injector()
                break  # avoid execute the else
        else:
            self.__logging.exception("SEARCH_FOR_PC_INDICATOR_FAILED")
            self.fault_injected = False