BitFlip.py 13.5 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
37
38
39
        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)

    """
    TODO: Describe the method
    """
German Leon's avatar
German Leon committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    def asmline(self):
      #Leo la linea de ejecucion
      return cf.execute_command(gdb=gdb, to_execute="x/1i $pc")
    def reg_asmline( self):
      #Obtengo los registros de la instruccion
      linea= self.asmline()
      self.__assmline="ASSM_LINE:{}".format(linea[0])
      #print (linea)#+"-"+str(len(linea))+"-"+linea[0])
      #print ("============")
      lista=re.findall(r"R(\d+)", linea[0])
      setlista=set()
      setlista.update(lista)
      #self.__logging.info(str(len(setlista)))
      return setlista
    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;
      #print (strlistareg)  
      self.__stringregs=strlistareg
      #self.__logging.info("REFERED REGISTERS :"+strlistareg)
      #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")
       #self.__logging.info("==============================="+self.__stringregs)
       linea= self.asmline()
       #self.__logging.info("ASSM_LINE:{}".format(linea))
       valores= cf.execute_command(gdb=gdb, to_execute=self.__stringregs)  
       return valores
    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
85
        #print(x)
German Leon's avatar
German Leon committed
86
87
88
        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
89
          #print (str(m.group(1))+":= "+str(m.group(2)))   
German Leon's avatar
German Leon committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
      #print(regs)
      #print ("========DR")
      return regs    
    def cmpregdst (self,valores,regs):
      #Comparo los regsitros, para saber cuales he modificado.
      #self.__logging.info("Execute cmpregdst------------")#+str(type(valores)))
      regdst=set()
      #print("==cmpregdst")
      #print("Arg;..valores...")
      #print(type(valores))
      #print (valores)
     
      
      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):
    #def miinst_out():
    	#self.__logging.info("Comienzo")
        regs=self.dictreg(self.regmod())
        #self.__logging.info("Reg1")
        valores=self.nextinstr()
        #self.__logging.info("Ejecutado sfte instuccion")
        r=self.cmpregdst(valores,regs)
        #self.__logging.info("Se han modificado {}".format(len(r)))
        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))
        self.__logging.info("ASSM_LINE:{}".format(self.__assmline))
        
                # __rf_generic_injector will set fault_injected attribute
        self.__rf_generic_injector()
     
German Leon's avatar
German Leon committed
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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
259
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
    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
            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
298
        #max_num_register = 
German Leon's avatar
German Leon committed
299
        # registers_list.popleft()
leon@uji.es's avatar
leon@uji.es committed
300
301
302
303
        #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
304
                #self.__logging.info ("LIne salida:{}".format(m.group(1))
leon@uji.es's avatar
leon@uji.es committed
305
        #         max_num_register += 1
German Leon's avatar
German Leon committed
306
                #self.__logging.info("LIne entrada {}--max{}".format(line,max_num_register))
German Leon's avatar
German Leon committed
307
308
309
        #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
310
311
312
313
    """
    Instruction injector    
    """

German Leon's avatar
German Leon committed
314
    def __inst_generic_injector_old(self):
German Leon's avatar
German Leon committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
        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