Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Add tools for transforming a kernel module into a guarded module
[palacios.git] / gears / guarded_modules / guard_callbacks_new.rb
1 #!/usr/bin/ruby
2
3 # Kyle C. Hale (c) 2012/2013
4 #
5 # Ruby implementation of a tool to identify the callbacks
6 # in a driver given the source for the driver and it's compiled kernel
7 # object. It does this by finding all static functions in the module and scanning
8 # the source to identify sites where a static function is being assigned to a variable.
9 #
10 # Dependencies: 
11 #               json ruby gem
12 #
13 # By default, all callbacks will be added as valid entry points into the module.
14 # For now, entries must manually be added and deleted by module author
15 #
16 # TODO:     
17 #           - remove hardcoded directories from this and chirsto script
18 #
19 #
20 # NOTES:
21 #
22 # - .i files are generated to confirm callback functions
23 #
24 # - source files and makefiles must be in same dir. Wont work for big
25 #   multi-level hierarchical source trees
26 #
27 # - this produces JSON output, including valid entry points, which 
28 #   (both entering into callback functions and returning from calls
29 #   to the kernel) can be modified by driver developer
30 #
31 # - this modifies the original driver source, as well as the original
32 #   driver Makefile
33 #
34 # - You must provide your **GUEST'S** System.map file for this script to reference
35
36 require 'optparse'
37 require 'rubygems'
38 require 'json'
39
40 CHRISTO = "/root/kyle/kyle/bnx/christo-s.pl"
41 GM_INIT = "v3_guard_mod_init"
42 GM_INIT_INTERNAL = "__guard_mod_init_internal"
43 GM_MACRO = "GM_ENTRY_REQUEST"
44 WRAP_FILE = "entry_wrapper.S"
45
46 GUARD_INIT_NR  = 0x6000
47 GUARD_ENTER_NR = 0x6001
48 GUARD_EXIT_NR  = 0x6002
49
50
51 def replace_callback_assigns(callbacks, file)
52
53 end
54
55 # ================== OPTIONS ========================= 
56
57 options = {}
58
59 optparse = OptionParser.new do|opts|
60
61   opts.banner = "Usage: guard_modules.rb [options] ..."
62
63   options[:verbose] = false
64   opts.on( '-v', '--verbose', 'Allow verbose output') do
65       options[:verbose] = true
66   end
67   
68   opts.on( '-h', '--help', 'Show this information') do
69       puts opts
70       exit
71   end
72
73   opts.on('-s', '--source <driver,src,files,...,>', '(C) source code for driver module') do |file|
74       options[:srcfile] = file.split(',')
75   end
76   
77   opts.on('-p', '--privs <requested,privileges>', 'Privileges requested. Currently numerical') do |privs|
78       options[:privs] = privs.split(',')
79   end
80
81   opts.on('-k', '--ko DRIVER KO', 'The compiled and linked kernel object for the driver') do |file|
82       options[:ko] = file
83   end
84   
85   opts.on('-m', '--makefile MAKEFILE', 'The driver\'s makefile') do |file|
86         options[:mf] = file
87   end
88
89   opts.on('-o', '--output OUTFILE', 'The file to which static module info will be written') do |file|
90     options[:out] = file
91   end
92
93   opts.on('-n', '--name NAME', 'The name of the guarded module') do |name|
94     options[:name] = name
95   end
96
97   options[:instr] = false
98   opts.on('i', '--instrument', 'Dump out the functions which are *NOT* to be instrumented, i.e. those that aren\'t callbacks')  do
99     options[:instr] = true
100   end
101 end
102
103
104 begin
105   optparse.parse!
106   mand = [:ko, :srcfile, :mf, :name]
107   missing = mand.select{ |parm| options[parm].nil? }
108   if not missing.empty?
109     puts "Missing options: #{missing.join(', ')}"
110     puts optparse
111     exit
112   end
113 rescue OptionParser::InvalidOption, OptionParser::MissingArgument
114   puts $!.to_s
115   puts optparse
116   exit
117 end
118
119 # ===================== END OPTIONS ==============================
120
121 $mkdir   = File.expand_path(File.dirname(options[:mf]))
122 $mkbase  = File.basename(options[:mf])
123 #TODO: this needs to be specified explicitly
124 $srcpath = options[:srcfile][0]
125 $srcdir  = File.expand_path(File.dirname(options[:srcfile][0]))
126 $srcbase = File.basename(options[:srcfile][0])
127
128 # global list of functions
129 # TODO: THIS IS NOT WORKING!!!! e.g., on bnx2_find_max_ring, objdump is not finding static funs
130 funcs = `objdump -t #{options[:ko]} | awk '\$3==\"F\" {print \$6}'`
131
132 # only static functions
133 stats = `objdump -t #{options[:ko]} | awk '\$2==\"l\"&&\$3==\"F\" {print \$6}'`
134
135 callbacks = []
136
137 puts "Running preprocessor..."
138 prep_files = {}
139
140 Dir.chdir($mkdir) do
141
142     # generate .i files (preprocessing)
143     ENV['PWD'] = Dir.pwd
144
145     abort("Problem cleaning") if not system "make clean > /dev/null"
146
147     #TODO parameterize this!
148     kerndir = "/v-test/guard_modules/gm_guest/kyle_gl/"
149
150     # run the kernel make process, and repeat the build of each .o with preprocessing directives
151     cmds = `make V=1 2>&1 | grep -P "gcc.*?\\s+\\-o\\s+.*?[^mod]\\.c" | sed 's/\\s\\+-c/ -E/g'| sed 's/\\(-o\\s\\+.*\\?\\)\\.o/\\1.i/g'`
152
153     Dir.chdir(kerndir) do
154         ENV['PWD'] = Dir.pwd
155
156         cmds.each do |i| 
157             abort ("Problem compiling.") if not system i
158         end
159     end
160
161     ENV['PWD'] = Dir.pwd
162
163     # read in preprocessed files
164     Dir.glob("#{$mkdir}/*.i", File::FNM_DOTMATCH).each do |fname|
165             prep_files[fname] = IO.readlines(fname)
166             puts "Reading in preprocessed file: #{fname}"
167             if not prep_files[fname]
168                 puts "could not open #{fname}"
169                 exit
170             end
171     end
172 end
173
174 ENV['PWD'] = Dir.pwd
175
176 stats.each do |stat|
177   s = stat.chomp
178   defs = []
179   
180   # look for callback assignments, this tells us that this
181   # static function is indeed being used as a callback
182   prep_files.each_value do |src|
183     tmp_defs = src.grep(/.*=\s*#{s}\s*(;|,)/)
184     defs.concat(tmp_defs) if not tmp_defs.empty?
185   end
186
187   callbacks.push(s) if not defs.empty?
188
189   if (options[:verbose]) 
190     puts "Possible callback assignment for #{s} : " if not defs.empty?
191     defs.each {|i| print "\t#{i}\n"}
192   end
193   
194 end
195
196 if (options[:instr])
197     f = funcs.split("\n")
198     
199     puts "Searching for driver's module_init routine..."
200     mod_init = nil
201
202     # TODO: make this work for multiple files
203     File.open($srcpath, 'r') do |file|
204         new_str = file.read
205         if new_str =~ /module_init\((.*?)\)/ 
206             puts "Found it, it's #{$1}" 
207             mod_init = $1
208         end
209     end
210     
211     # back up (TODO: ALL) source/makefiles
212     `cp #{$mkdir}/#{$mkbase} #{$mkdir}/#{$mkbase}.bak`
213     `cp #{options[:srcfile][0]} #{$srcdir}/#{$srcbase}.bak`
214
215     # generate a restore script
216     shell = `which sh`
217     restore = <<-eos
218 #!#{shell}
219 for i in `ls *.bak | sed 's/\.bak//g'`; do mv $i.bak $i; done
220 make clean && make
221 rm -f *.i
222 eos
223
224     # TODO: WARNING WARNING: assumes source files and makefiles are in same place
225     File.open("#{$srcdir}/gm_restore.sh", "w", 0744) do |file|
226         file.puts restore 
227     end
228
229     str = <<-eos
230 /* BEGIN GENERATED */
231 #define V3_GUARD_ENTER_HCALL_NR #{GUARD_ENTER_NR}
232 #define V3_GUARD_EXIT_HCALL_NR  #{GUARD_EXIT_NR}
233 #define V3_GUARD_INIT_HCALL_NR  #{GUARD_INIT_NR}
234 #define V3_GUARDED_MODULE_ID   0xA3AEEA3AEEBADBADULL
235 #define #{GM_MACRO}(fun) _gm_entry_req_##fun
236 int __init #{GM_INIT} (void);
237 eos
238
239 # for assembly linkage
240 callbacks.each do |c| 
241     str +=<<-eos "int _gm_entry_req_#{c}(void);\n"
242 __asm__(".globl _gm_entry_req_#{c};\\
243    _gm_entry_req_#{c}:\\
244    popq  %r11;          \\
245    pushq %rax;\\
246    movq  $#{GUARD_ENTER_NR}, %rax;\\
247    vmmcall;\\
248    popq  %rax;\\
249    callq #{c};\\
250    pushq %rax;\\
251    movq  $#{GUARD_EXIT_NR}, %rax;\\
252    vmmcall;\\
253    popq  %rax;\\
254    pushq %r11;\\
255    ret;");
256 eos
257 end
258 str += "/* END GENREATED */\n"
259
260 end_str = <<-eos
261 /* BEGIN GENERATED */
262 int #{GM_INIT_INTERNAL} (void) __attribute__((section (".text"),no_instrument_function));
263
264 int #{GM_INIT_INTERNAL} (void) {
265         int ret, ret_orig;
266
267         /* GUARD INIT */
268         __asm__ __volatile__ ("vmmcall"
269                       :"=a" (ret)
270                       :"0"  (V3_GUARD_INIT_HCALL_NR), "b" (V3_GUARDED_MODULE_ID));
271
272         if (ret < 0) {
273                 printk("Guest GM: error initializing guarded module\\n");
274         } else {
275                 printk("Guest GM: successfully initialized guarded module\\n");
276         }
277
278         ret_orig = #{mod_init}();
279
280         if (ret_orig < 0) { 
281                 printk("Guest GM: error calling original init\\n");
282         } else {
283                 printk("Guest GM: successfully called original driver init\\n");
284         }
285
286         /* GUARD EXIT */
287         __asm__ __volatile__("vmmcall" : "=a" (ret) : "0" (V3_GUARD_EXIT_HCALL_NR));
288
289         if (ret < 0) {
290                 printk("Guest GM: error doing initial exit\\n");
291                 return ret;
292         }
293
294         return ret_orig;
295 }
296
297 int __init #{GM_INIT} (void) {
298         return #{GM_INIT_INTERNAL}();
299 }
300
301 /* END GENERATED */
302 eos
303
304 # GENERATE THE CALLBACK WRAPPERS
305 #
306 # statements like 
307 # .callback = myfunc;
308 #
309 # will be replaced with 
310 # .callback = GM_ENTRY_REQUEST(myfunc);
311 #
312 # which expands to 
313 # .callback = __gm_entry_req_myfunc;  (this is an assembly stub)
314 #
315
316     callbacks.each do |c|
317       end_str += <<-eos
318       void  (*#{c}_ptr)() = #{c};
319 eos
320     end
321
322     end_str += "/* END GENERATED */"
323
324     # append instrumentation functions to end of source file
325     new_str = ""
326     File.open($srcpath, "r") do |file|
327         new_str = file.read
328     end
329
330     # fixup module init calls with our own
331     new_str = new_str.gsub(/__init #{mod_init}/, "#{mod_init}")
332     new_str = new_str.gsub(/module_init\(.*?\);/, "module_init(#{GM_INIT});")
333
334     # fixup callback assignments with our macro
335     # TODO: make this work for multiple source files
336     callbacks.each do |c|
337         new_str = new_str.gsub(/(.*=\s*)#{c}(\s*(;|,))/) {
338             "#{$1}#{GM_MACRO}(#{c})#{$2}"
339         }
340     end
341
342     # put these at the top of the file, after the includes
343     new_str = new_str.insert(new_str.rindex(/#include.*?[>"]/), str)
344     new_str += end_str
345     
346     File.open($srcpath, "w+") do |file|
347         file.puts new_str
348     end
349
350     Dir.chdir($mkdir) do
351         abort ("Error cleaning") if not system "make clean > /dev/null 2>&1"
352         abort("Error rebuilding module") if not system "make > /dev/null 2>&1"
353     end
354
355 end
356
357 # if an outfile is specified, output static module info
358 # in JSON format
359 if (options[:out])
360   # First generate wrapped module
361
362   ko = File.expand_path(options[:ko])
363   puts "Running christo with Kernel object: " + ko
364   christo_output = `#{CHRISTO} --kern #{ko} 2>&1 | grep ACCEPT`
365   out_arr = []
366   christo_output.each_line { |x| out_arr.push(x.chomp.gsub(/\s*ACCEPT\s*/, '')) }
367
368   wrap_name = File.basename(ko).sub(/\.ko$/,'_wrapped.ko')
369
370   ret_points = []
371   out_arr.each do |x|
372       ret_points.push Array[x, `objdump -d #{wrap_name} | sed -n '/__wrap_#{x}>/,/ret/p' | grep vmmcall | tail -1 | cut -f1 -d':' | sed 's/\s//g'`.to_i(16)]
373   end
374
375   abort("Problem building wrapped module") if not system "make > /dev/null 2>&1"
376   static_info = {}
377   static_info['module_name'] = options[:name]
378
379   # get the content hash of .text
380   static_info['content_hash'] = `objdump -h #{wrap_name} | grep -P "\s+\.text\s+" | awk '{print "dd if=#{wrap_name} bs=1 count=$[0x" $3 "] skip=$[0x" $6 "] 2>/dev/null"}' | bash | md5sum - | awk '{print $1}'`.chomp
381   static_info['size'] = `objdump -h #{wrap_name} | grep -P "\s+\.text\s+" | awk '{print $3}'`.to_i(16)
382
383   # find offset to hcall (we've ensured it's in .text)
384   static_info['hcall_offset'] = `objdump -d #{wrap_name} | sed -n '/#{GM_INIT_INTERNAL}/,/ret/p'| grep vmmcall | cut -f1 -d':'|sed 's/\s//g'`.to_i(16)
385
386   # each callback function will have ONE valid entry point, the address of the VMMCALL
387   # in its function body
388   static_info['entry_points'] = callbacks.collect do |c|
389       c_off = `objdump -d #{wrap_name} | sed -n '/_gm_entry_req_#{c}>/,/vmmcall/p' | tail -1 | cut -f1 -d':' | sed 's/\s//g'`.to_i(16)
390       Array[c, c_off]
391   end
392
393   static_info['ret_points'] = ret_points
394   static_info['privileges'] = options[:privs]
395
396   File.open(options[:out], "w") do |file|
397       file.write(JSON.pretty_generate(static_info))
398   end
399
400 else
401   callbacks.each {|c| puts c }
402 end