Skip to content
This repository
Browse code

Add compiler signature/version checking

The verison field in .rbc files now contains a 64bit signature
calculated from the compiler and opcodes. .rbc are validated against
this signature to see if they're valid to use.
  • Loading branch information...
commit 6fa3dc44137f63452d06e9d45e37a04dead665a5 1 parent 5447903
authored January 04, 2010
35  kernel/delta/requirer.rb
... ...
@@ -1,12 +1,5 @@
1 1
 module Rubinius
2 2
 
3  
-  ##
4  
-  # This const controls what the lowest version of compiled methods we can
5  
-  # allow is. This allows us to cut off compability at some point, or just
6  
-  # increment when major changes are made to the compiler.
7  
-
8  
-  CompiledMethodVersion = 6
9  
-
10 3
   def self.compile_file(file, save=true)
11 4
     cm = Rubinius::CompilerNG.compile_file file, 1
12 5
 
@@ -22,6 +15,8 @@ def self.compile_file(file, save=true)
22 15
 
23 16
     return cm
24 17
   end
  18
+
  19
+  class InvalidRBC < RuntimeError; end
25 20
 end
26 21
 
27 22
 ##
@@ -31,20 +26,27 @@ def self.compile_file(file, save=true)
31 26
 class Requirer
32 27
   # TODO: This is temporary until the compiler is refactored
33 28
   def self.version_number
34  
-    42 # No question mark. This is fact.
  29
+    Rubinius::Signature
35 30
   end
36 31
 
37 32
   # TODO: This is temporary until the compiler is refactored
38 33
   module Utils
39 34
 
40  
-  @load_rbc_directly = false
41  
-
42 35
   def self.compiler
43 36
     return ::Requirer
44 37
   end
45 38
 
46 39
   def self.version_number
47  
-    return self.compiler ? self.compiler.version_number : 0
  40
+    Rubinius::Signature
  41
+  end
  42
+
  43
+  @load_rbc_directly = false
  44
+  def self.loading_rbc_only(val=true)
  45
+    old = @load_rbc_directly
  46
+    @load_rbc_directly = val
  47
+    yield
  48
+  ensure
  49
+    @load_rbc_directly = old
48 50
   end
49 51
 
50 52
   def self.execute(string)
@@ -65,7 +67,8 @@ def self.__unexpected_break__
65 67
 
66 68
   def self.load_from_rbc(path, version)
67 69
     Ruby.primitive :compiledfile_load
68  
-    raise PrimitiveFailure, "CompiledFile.load_from_rbc primitive failed"
  70
+
  71
+    raise Rubinius::InvalidRBC, path
69 72
 
70 73
     # HACK: remove the primitive above when compiled_file.rb
71 74
     # unmarshal_data method is fixed and ruby performance is better
@@ -178,8 +181,9 @@ def self.single_load(dir, rb, rbc, ext, requiring, options)
178 181
         if @load_rbc_directly
179 182
 
180 183
           if rbc_stat and rbc_stat.file?
  184
+            ver = (@load_rbc_directly == :force ? 0 : version_number)
181 185
             compile_feature(rb, requiring) do
182  
-              cm = load_from_rbc(rbc_path, version_number)
  186
+              cm = load_from_rbc(rbc_path, ver)
183 187
               raise LoadError, "Invalid .rbc: #{rbc_path}" unless cm
184 188
             end
185 189
           else
@@ -217,6 +221,11 @@ def self.single_load(dir, rb, rbc, ext, requiring, options)
217 221
           compile_feature(rb, requiring) do
218 222
             begin
219 223
               cm = load_from_rbc(rbc_path, version_number)
  224
+            rescue InvalidRBC
  225
+              if $DEBUG_LOADING
  226
+                STDERR.puts "[Invalid .rbc #{rbc_path} detected, ignoring]"
  227
+              end
  228
+              cm = nil
220 229
             rescue TypeError, IOError
221 230
               cm = nil
222 231
             end
39  kernel/loader.rb
@@ -300,6 +300,13 @@ def options(argv=ARGV)
300 300
         puts
301 301
       end
302 302
 
  303
+      # This will only trigger if it's not the first option, in which case
  304
+      # we'll just tell the user to make it the first option.
  305
+      options.on "--rebuild-compiler", "Rebuild the Rubinius compiler" do
  306
+        puts "This must be the first and only option."
  307
+        exit 1
  308
+      end
  309
+
303 310
       options.doc <<-DOC
304 311
 \nVM Options
305 312
    -X<variable>[=<value>]
@@ -329,7 +336,37 @@ def load_paths
329 336
     end
330 337
 
331 338
     def load_compiler
332  
-      require "compiler"
  339
+      # This happens before we parse ARGV, so we have to check ARGV ourselves
  340
+      # here.
  341
+
  342
+      rebuild = (ARGV.last == "--rebuild-compiler")
  343
+
  344
+      begin
  345
+        Requirer::Utils.loading_rbc_only(rebuild ? :force : true) do
  346
+          require "compiler"
  347
+        end
  348
+      rescue Rubinius::InvalidRBC => e
  349
+        STDERR.puts "There was an error loading the compiler."
  350
+        STDERR.puts "It appears that your compiler is out of date with the VM."
  351
+        STDERR.puts "\nPlease use 'rbx --rebuild-compiler' or 'rake [instal]' to"
  352
+        STDERR.puts "bring the compiler back to date."
  353
+        exit 1
  354
+      end
  355
+
  356
+      if rebuild
  357
+        STDOUT.puts "Rebuilding compiler..."
  358
+        files =
  359
+          ["#{@main_lib}/compiler.rb"] +
  360
+          Dir["#{@main_lib}/compiler/*.rb"] +
  361
+          Dir["#{@main_lib}/compiler/**/*.rb"]
  362
+
  363
+        files.each do |file|
  364
+          puts "#{file}"
  365
+          Rubinius.compile_file file, true
  366
+        end
  367
+
  368
+        exit 0
  369
+      end
333 370
     end
334 371
 
335 372
     # Require any -r arguments
2  lib/compiler/compiled_file.rb
@@ -37,7 +37,7 @@ def self.load(stream)
37 37
     # Writes the CompiledFile +cm+ to +file+.
38 38
     def self.dump(cm, file)
39 39
       File.open(file, "w") do |f|
40  
-        new("!RBIX", 0, "x").encode_to(f, cm)
  40
+        new("!RBIX", Rubinius::Signature, "x").encode_to(f, cm)
41 41
       end
42 42
     rescue Errno::EACCES
43 43
       # just skip writing the compiled file if we don't have permissions
2  lib/compiler/generator.rb
@@ -163,6 +163,8 @@ def package(klass)
163 163
       cm
164 164
     end
165 165
 
  166
+    # Replace all Labels in the stream by dereferencing them to their integer
  167
+    # position.
166 168
     def set_label_positions
167 169
       @stream.each_with_index do |op, index|
168 170
         if op.kind_of? Label
12  rakelib/kernel.rake
@@ -89,7 +89,7 @@ compiler_files = FileList[
89 89
 compiler_files.each do |rb|
90 90
   rbc = rb + "c"
91 91
 
92  
-  file rbc => rb
  92
+  file rbc => [rb, compiler_signature]
93 93
   runtime << rbc
94 94
 end
95 95
 
@@ -113,11 +113,19 @@ file compiler_signature => compiler_files + parser_ext_files do |t|
113 113
     end
114 114
   end
115 115
 
  116
+  # Collapse the digest to a 64bit quantity
  117
+  hd = digest.hexdigest
  118
+  hash = hd[0, 16].to_i(16) ^ hd[16,16].to_i(16) ^ hd[32,8].to_i(16)
  119
+
116 120
   File.open t.name, "w" do |file|
117 121
     file.puts "# This file is generated by rakelib/kernel.rake. The signature"
118 122
     file.puts "# is used to ensure that only current .rbc files are loaded."
119 123
     file.puts "#"
120  
-    file.puts "Rubinius::Signature = 0x#{digest.to_s[0, 16]}"
  124
+    file.puts "Rubinius::Signature = #{hash}"
  125
+  end
  126
+
  127
+  File.open "runtime/signature", "w" do |file|
  128
+    file.puts hash
121 129
   end
122 130
 end
123 131
 
15  vm/builtin/system.cpp
@@ -77,23 +77,24 @@ namespace rubinius {
77 77
   //
78 78
   // HACK: remove this when performance is better and compiled_file.rb
79 79
   // unmarshal_data method works.
80  
-  Object* System::compiledfile_load(STATE, String* path, Object* version) {
  80
+  Object* System::compiledfile_load(STATE, String* path, Integer* version) {
81 81
     if(!state->probe->nil_p()) {
82 82
       state->probe->load_runtime(state, std::string(path->c_str()));
83 83
     }
84 84
 
85 85
     std::ifstream stream(path->c_str());
86 86
     if(!stream) {
87  
-      std::ostringstream msg;
88  
-      msg << "unable to open file to run: " << path->c_str();
89  
-      Exception::io_error(state, msg.str().c_str());
  87
+      return Primitives::failure();
90 88
     }
91 89
 
92 90
     CompiledFile* cf = CompiledFile::load(stream);
93 91
     if(cf->magic != "!RBIX") {
94  
-      std::ostringstream msg;
95  
-      msg << "Invalid file: " << path->c_str();
96  
-      Exception::io_error(state, msg.str().c_str());
  92
+      return Primitives::failure();
  93
+    }
  94
+
  95
+    uint64_t ver = version->to_ulong_long();
  96
+    if(ver > 0 && cf->version > 0 && cf->version != ver) {
  97
+      return Primitives::failure();
97 98
     }
98 99
 
99 100
     Object *body = cf->body(state);
2  vm/builtin/system.hpp
@@ -34,7 +34,7 @@ namespace rubinius {
34 34
 
35 35
     /** Load a compiled file. */
36 36
     // Ruby.primitive :compiledfile_load
37  
-    static Object*  compiledfile_load(STATE, String* path, Object* version);
  37
+    static Object*  compiledfile_load(STATE, String* path, Integer* version);
38 38
 
39 39
     /**
40 40
      *  When running under GDB, stop here.
2  vm/compiled_file.cpp
@@ -20,7 +20,7 @@
20 20
 namespace rubinius {
21 21
   CompiledFile* CompiledFile::load(std::istream& stream) {
22 22
     std::string magic, sum;
23  
-    long ver;
  23
+    uint64_t ver;
24 24
 
25 25
     stream >> magic;
26 26
     stream >> ver;
14  vm/compiled_file.hpp
@@ -13,18 +13,20 @@ namespace rubinius {
13 13
   class CompiledFile {
14 14
   public:
15 15
     std::string magic;
16  
-    long version;
  16
+    uint64_t version;
17 17
     std::string sum;
18 18
 
19 19
   private:
20 20
     std::istream* stream;
21 21
 
22 22
   public:
23  
-    CompiledFile(std::string magic, long version, std::string sum, 
24  
-        std::istream* stream) : 
25  
-          magic(magic), version(version), sum(sum), 
26  
-          stream(stream) { }
27  
-
  23
+    CompiledFile(std::string magic, uint64_t version, std::string sum,
  24
+                 std::istream* stream)
  25
+      : magic(magic)
  26
+      , version(version)
  27
+      , sum(sum)
  28
+      , stream(stream)
  29
+    {}
28 30
 
29 31
     static CompiledFile* load(std::istream& stream);
30 32
     Object* body(STATE);
17  vm/drivers/cli.cpp
@@ -89,13 +89,22 @@ int main(int argc, char** argv) {
89 89
 
90 90
     std::cout << "Ruby backtrace:" << std::endl;
91 91
     env.state->print_backtrace();
92  
-  } catch(std::runtime_error& e) {
93  
-    std::cout << "Runtime exception: " << e.what() << std::endl;
  92
+  } catch(BadKernelFile& e) {
  93
+    std::cout << "ERROR: BadKernelFile: " << e.what() << "\n\n";
  94
+    std::cout << "An invalid kernel file has been detected.\n";
  95
+    std::cout << "This is because the VM is out of sync with the kernel.\n";
  96
+    std::cout << "Please recompile your kernel using:\n";
  97
+    std::cout << "  rake kernel:clean clean\n";
  98
+    std::cout << "  rake\n";
  99
+    std::cout << "\nIf the problem persists, please open an issue at:\n";
  100
+    std::cout << "  http://github.com/evanphx/rubinius\n";
  101
+    std::cout << "\nThanks,\n  Management.\n";
  102
+    return 1;
94 103
   } catch(VMException &e) {
95 104
     std::cout << "Unknown VM exception detected." << std::endl;
96 105
     e.print_backtrace();
97  
-  } catch(std::string e) {
98  
-    std::cout << e << std::endl;
  106
+  } catch(std::runtime_error& e) {
  107
+    std::cout << "Runtime exception: " << e.what() << std::endl;
99 108
   } catch(...) {
100 109
     std::cout << "Unknown exception detected." << std::endl;
101 110
   }
21  vm/environment.cpp
@@ -42,6 +42,7 @@ namespace rubinius {
42 42
   Environment::Environment(int argc, char** argv)
43 43
     : argc_(argc)
44 44
     , argv_(argv)
  45
+    , signature_(0)
45 46
     , agent(0)
46 47
   {
47 48
 #ifdef ENABLE_LLVM
@@ -331,6 +332,9 @@ namespace rubinius {
331 332
 
332 333
     CompiledFile* cf = CompiledFile::load(stream);
333 334
     if(cf->magic != "!RBIX") throw std::runtime_error("Invalid file");
  335
+    if(signature_ > 0) {
  336
+      if(cf->version != signature_) throw BadKernelFile(file);
  337
+    }
334 338
 
335 339
     /** @todo Redundant? CompiledFile::execute() does this. --rue */
336 340
     state->thread_state()->clear_exception(true);
@@ -413,8 +417,25 @@ namespace rubinius {
413 417
 
414 418
     // Load the ruby file to prepare for bootstrapping Ruby!
415 419
     // The bootstrapping for the VM is already done by the time we're here.
  420
+
  421
+    // First, pull in the signature file. This helps control when .rbc files need
  422
+    // to be discarded.
  423
+
  424
+    std::string sig_path = root + "/signature";
  425
+    std::ifstream sig_stream(sig_path.c_str());
  426
+    if(sig_stream) {
  427
+      sig_stream >> signature_;
  428
+      state->globals.rubinius->set_const(state, "Signature",
  429
+                       Integer::from(state, signature_));
  430
+      sig_stream.close();
  431
+    } else {
  432
+      state->globals.rubinius->set_const(state, "Signature", Integer::from(state, 0));
  433
+    }
  434
+
  435
+    // Load alpha
416 436
     run_file(root + "/alpha.rbc");
417 437
 
  438
+    // Read the index and load the directories listed.
418 439
     while(!stream.eof()) {
419 440
       std::string line;
420 441
 
10  vm/environment.hpp
@@ -13,6 +13,14 @@ namespace rubinius {
13 13
   class ConfigParser;
14 14
   class QueryAgent;
15 15
 
  16
+  // throw when there is a bad signature on a kernel .rbc file.
  17
+  class BadKernelFile : public std::runtime_error {
  18
+  public:
  19
+    BadKernelFile(const std::string& str)
  20
+      : std::runtime_error(str)
  21
+    {}
  22
+  };
  23
+
16 24
   class Environment {
17 25
     // The thread used to trigger preemptive thread switching
18 26
     pthread_t preemption_thread_;
@@ -20,6 +28,8 @@ namespace rubinius {
20 28
     int argc_;
21 29
     char** argv_;
22 30
 
  31
+    uint64_t signature_;
  32
+
23 33
   public:
24 34
     SharedState* shared;
25 35
     VM* state;
2  vm/test/test_compiled_file.hpp
@@ -32,7 +32,7 @@ class TestCompiledFile : public CxxTest::TestSuite, public VMTest {
32 32
 
33 33
     CompiledFile* cf = CompiledFile::load(stream);
34 34
     TS_ASSERT_EQUALS(cf->magic, std::string("!RBIX"));
35  
-    TS_ASSERT_EQUALS(cf->version, 1);
  35
+    TS_ASSERT_EQUALS(cf->version, 1ULL);
36 36
     TS_ASSERT_EQUALS(cf->sum, std::string("aoeu"));
37 37
     TS_ASSERT_EQUALS((long)stream.tellg(), 13);
38 38
   }

0 notes on commit 6fa3dc4

Please sign in to comment.
Something went wrong with that request. Please try again.