Archive for the ‘Uncategorized’ Category

Applying backported security patches to Rails 2.3

January 5, 2013

If you are still running a mission-critical app on Rails 2.3, then you are a poor schmuck, and I salute you.

There are many helpful souls out there who will tell you you need to upgrade, but as we all know the upgrade path from Rails 2 to 3 (to 4) is hardly plain sailing, and there may be good reasons why this is not currently practical.  So taking it as read that upgrading right now is not an option, let’s move on.

The main concern with running 2.3 is that it is no longer officially supported, and so security patches need to be backported manually.  A good example of this happened this week with CVE-2012-5664 here.

In this case the Rails team released a patch file, which in fact actually got merged with the 2-3-stable branch of the Rails git repository (see commit log), but if you’re using the Rails 2.3.14 gem then this is no good to you.  The Rails team would need to release 2.3.15 which doesn’t look likely.

So the options are:

  1. Reopen the affected classes/modules and apply the patch(es) in your own app.  Yuck.
  2. Manually patch the installed gem on all your servers.  Yuck.
  3. Freeze the latest patched 2-3-stable branch into your app’s vendor directory.  OK, but  this would bloat your app’s repo and would slow deployments.
  4. Stop using the official 2.3.14 gem and have bundler link against the patched 2-3-stable branch via git

I chose to go with option 4.

Ideally you would just need to stick this in your app’s Gemfile:

#gem "rails", "~> 2.3.14"
git "git://github.com/rails/rails.git", :branch => '2-3-stable' do
  gem 'rails'
  gem 'actionmailer'
  gem 'actionpack'
  gem 'activerecord'
  gem 'activeresource'
  gem 'activesupport'
end

but this will not work, for reasons explained in this helpful stackoverflow post.  Basically, the 2-3 branch of Rails doesn’t have gemspecs, because these used to be created dynamically by Rake.  Bundler requires the gemspecs to sort out the dependencies, so we’re screwed.

As described in the stackoverflow post, the solution is to fork the official Rails repo, and add the gemspecs yourself.  The author of the post tried to adapt the Rakefiles to create the gemspecs, but didn’t quite get it right – his files will work, but they screw up the require sequence, which results in all kinds of warnings about redeclared constants.  But there is no need to do this – you already have the gemspecs in your installed Rails 2.3.14 gem:

$ cd <path to my app>
$ bundle show rails
/Users/robanderson/.rvm/gems/ruby-1.8.7-p370/gems/rails-2.3.14
$ cd /Users/robanderson/.rvm/gems/ruby-1.8.7-p370/specifications
$ ls
actionmailer-2.3.14.gemspec
actionpack-2.3.14.gemspec
activerecord-2.3.14.gemspec
activeresource-2.3.14.gemspec
activesupport-2.3.14.gemspec
[...]
rails-2.3.14.gemspec

So in nutshell here is what you need to do:

  1. Fork the official rails repo. You could just use my fork but you shouldn’t because I could be evil.
  2. Check out your repo and switch to the 2-3-stable branch:
    $ git clone git@github.com:rob-anderson/rails.git
    Cloning into 'rails'...
    remote: Counting objects: 343770, done.
    remote: Compressing objects: 100% (86191/86191), done.
    remote: Total 343770 (delta 266893), reused 329719 (delta 254435)
    Receiving objects: 100% (343770/343770), 51.39 MiB | 564 KiB/s, done.
    Resolving deltas: 100% (266893/266893), done.
    $ cd rails
    $ git checkout 2-3-stable
    Branch 2-3-stable set up to track remote branch 2-3-stable from origin.
    Switched to a new branch '2-3-stable'
    
  3. Add the following gemspecs, copying them from your installed gemspecs above (the railties gemspec should be copied from the rails-2.3.14.gemspec):
    • actionmailer/actionmailer.gemspec
    • actionpack/actionpack.gemspec
    • activerecord/activerecord.gemspec
    • activeresource/activeresource.gemspec
    • activesupport/activesupport.gemspec
    • railties/railties.gemspec
  4. Commit and push your changes:

    $ git add .
    $ git status
    # On branch 2-3-stable
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #	new file:   actionmailer/actionmailer.gemspec
    #	new file:   actionpack/actionpack.gemspec
    #	new file:   activerecord/activerecord.gemspec
    #	new file:   activeresource/activeresource.gemspec
    #	new file:   activesupport/activesupport.gemspec
    #	new file:   railties/railties.gemspec
    #
    $ git commit -a -m "Gemspecs to allow rails 2-3-stable to be linked directly from a gemfile"
    6 files changed, 635 insertions(+)
     create mode 100644 actionmailer/actionmailer.gemspec
     create mode 100644 actionpack/actionpack.gemspec
     create mode 100644 activerecord/activerecord.gemspec
     create mode 100644 activeresource/activeresource.gemspec
     create mode 100644 activesupport/activesupport.gemspec
     create mode 100644 railties/railties.gemspec
    $ git push
    Counting objects: 21, done.
    Delta compression using up to 8 threads.
    Compressing objects: 100% (14/14), done.
    Writing objects: 100% (14/14), 8.44 KiB, done.
    Total 14 (delta 8), reused 0 (delta 0)
    To git@github.com:rob-anderson/rails.git
    
  5. Update your app’s gemfile to link to your fork:

    #gem "rails", "~> 2.3.14"
    git "git://github.com/rob-anderson/rails.git", :branch => "2-3-stable" do
      gem 'rails'
      gem 'actionmailer'
      gem 'actionpack'
      gem 'activerecord'
      gem 'activeresource'
      gem 'activesupport'
    end
    
    

    and run bundle install. You should now be cooking with gas.

Of course, there is no guarantee that future patches will be backported by the Rails team, or applied to the official Rails repo. But by having your own fork, you can patch at will, from whatever source – and you can always merge future changes from the official repo.

P3P header hell

February 25, 2011

If you’ve ever embedded a Rails app in an iframe, then you have probably come across a big problem: IE will refuse cookies from the embedded site, so your session info gets trashed.

There’s lots of info on the web about this “feature” of IE, and the solution involves setting a P3P header to return details of your “privacy policy”. Chapter and verse here

The simple Rails equivalent is this:

class ApplicationController < ActionController::Base
  
  before_filter :set_p3p
  
  private
  
  # this is required by IE so that we can set session cookies
  def set_p3p
    headers['P3P'] = 'CP="ALL DSP COR CURa ADMa DEVa OUR IND COM NAV"'
  end
end

At first sight this seems to work. But don’t get too excited: after a few more page refreshes, it suddenly stops working, IE trashes the session, and your user is mysteriously logged out of your app.

The reason is described, along with a solution, here. In short, Rails’ etagging functionality, which is enabled by default (at least, it is in 2.3.8) means that sooner or later IE is going to request a page which Rails will know has not changed in the interim. When this happens Rails returns a 304 Not Changed header, which crucially doesn’t include your oh-so-important P3P header. Net result – IE trashes the session.

You can mangle Rails to set the P3P header regardless, but as the W3C prohibits these headers in this type of response, some webservers, including Apache, will strip them out again.

The very helpful solution described above will work, but at the expense of disabling etags for the entire app. This negates the benefit of etags and will result in slower navigation for your users, and increased load on your server. So why should everyone suffer because of Microsoft’s much-loathed browser?

The solution is to disable etags only if the browser is IE, leaving it switched on for everyone else. Stick this in your /lib directory and require it from your environment.rb:

# The purpose of this is to disable etags when processing requests from Internet Explorer
# This is because IE requires a P3P header in order to accept cookies within an iframe
# and if etags are used then subsequent calls for the same page are met with a 304 not changed
# response, which is not allowed (by W3C) to contain P3P headers.  Apache strips them even if we
# patch Rails to set them anyway

# see refs:
# Apache filters P3P headers from 304 responses: http://groups.google.com/group/rack-devel/browse_thread/thread/11da5971522b107b
# general explanation of the problem: http://tempe.st/tag/ruby-on-rails/

module ActionController
  
  class Request
    
    alias_method :etag_matches_original?, :etag_matches?
    
    def etag_matches?(etag)
      !env['HTTP_USER_AGENT'].include?('MSIE') && etag_matches_original?(etag)
    end
  
  end
 
  class Response
    
    alias_method :etag_original?, :etag?
    
    def etag?
      request.env['HTTP_USER_AGENT'].include?('MSIE') || etag_original?
    end
    
    
  end
end

Visa debit / Delta reg exp

January 11, 2010

I couldn’t find this anywhere so I had to write this myself, based on this document from Barclays


VISA_DEBIT = /^(400626|40854[789]|40940[012]|41228[56]|41373[34567]|41378[78]|418760|41917[6789]|419772|420672|42159[234]|422793|423769|431072|444001|44400[5678]|44620[0-9]|44621[01]|44621[3-9]|4462[2-4][0-9]|44625[0-4]|44625[7-9]|44626[0-9]|44627[0-2]|44627[4-9]|44628[0-3]|446286|446294|450875|45397[89]|454313|45443[2-5]|454742|45672[5-9]|45673[0-9]|456724[0-5]|4658[3-7][0-9]|46590[1-9]|4659[1-4][0-9]|465950|4751[1-5][0-9]|4757[1-5][0-9]|4762[2-6][0-9]|4763[4-8][0-9]|484409|484410|484427|4909[6-7][0-9]|49218[12])\d{10}/

Fling for the iPhone – how to cheat

November 16, 2009

The game Fling for the iPhone is simple but annoyingly difficult. I feel I should be better at it.

It does let you cheat, but penalises you for doing so – basically it adds an extra game each time you do it, so you never level up.

In an exercise which proves yet again that my Mac is more intelligent than I am, I wrote a script to solve the puzzles. You can download it here

compiling Ruby 1.8.6 on Snow Leopard

October 5, 2009

I recently got a new MacBook Pro running Snow Leopard. I mostly got everything working the way I wanted it, but I found that the default Ruby installation is now 1.8.7, which doesn’t work for us – because we use WSS4R which is only compatible up to 1.8.6 (I wish this weren’t so, hoping to persuade the maintainer to patch and upgrade)

So I decided to download the source for Ruby 1.8.6 and install it in /usr/local – adapting very helpful instructions found on Hivelogic here

It turns out though that you can’t compile versions of Ruby pre 1.8.7p72 on Snow Leopard. I ran into this error on my new laptop and also on a colleague’s older one which he had upgraded with a Snow Leopard upgrade disc. When running make, the following error is thrown:


In file included from openssl_missing.c:22:
openssl_missing.h:119: error: conflicting types for 'BN_rand_range'
/usr/include/openssl/bn.h:411: error: previous declaration of 'BN_rand_range' was here
openssl_missing.h:120: error: conflicting types for 'BN_pseudo_rand_range'
/usr/include/openssl/bn.h:412: error: previous declaration of 'BN_pseudo_rand_range' was here
make[1]: *** [openssl_missing.o] Error 1
make: *** [all] Error 1

There are quite a few hits on this on Google, mainly relating to various Linux distros. Based on this Gentoo patch I figured you need to download the Ruby 1.8.7p72 tarball, and copy the following files from it to your 1.8.6 source:

(all subdirectories relative to the source root)
/ext/openssl/extconf.rb
/ext/openssl/openssl_missing.c
/ext/openssl/openssl_missing.h
/ext/openssl/ossl_hmac.c

Having done that I reran the ./configure, and then tried make again – and it worked.

No idea what the patched files do, but all seems stable so far.

can’t be referred hell

September 15, 2009

Recently we upgraded from Rails 1.2.6 to 2.3.4, which was long overdue.  As you’d expect, lots of things broke, plugins needed updating etc.

Then we got this in the logs:

/!\ FAILSAFE /!\ Tue Sep 15 15:19:22 +0100 2009
Status: 500 Internal Server Error
Card can't be referred
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `dump'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:94:in `marshal'
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/session_store.rb:150:in `marshal_data!'

Card being one of the models which we were trying to save into the session. I know that not everyone saves entire models into the session – I might write a post about that too. But whatever your preference, it should work, providing you are not serializing an object which can’t be serialized (more on that below).

It took days of messing about to figure out what to do about this. The quick answer is: require the model explicitly in the environment.rb file. No idea why. Only one of our 30+ models was affected, and there is nothing special about it that I can see.

If you’re not in a hurry, here’s the low-down on what we found out.

The error is thrown in Marshal.c, in the following code

00101 static VALUE
00102 class2path(klass)
00103     VALUE klass;
00104 {
00105     VALUE path = rb_class_path(klass);
00106     char *n = RSTRING(path)->ptr;
00107 
00108     if (n[0] == '#') {
00109         rb_raise(rb_eTypeError, "can't dump anonymous %s %s",
00110                  (TYPE(klass) == T_CLASS ? "class" : "module"),
00111                  n);
00112     }
00113     if (rb_path2class(n) != rb_class_real(klass)) {
00114         rb_raise(rb_eTypeError, "%s can't be referred", n);
00115     }
00116     return path;
00117 }

I don’t read C but I am told this means something along the lines of “check if when we recreate this object we end up with the same class and ancestry as the object we are trying to serialize”. For some reason, our Card object was failing this check.

Inserting a ruby-style equivalent in the ActiveRecord::Session::SessionStore code immediately before the call to Marshal.dump eg

data[:card].class.ancestors == Card.new.class.ancestors

failed to pick up the problem. Also, we couldn’t recreate this in the console no matter what – the object always serialized perfectly.

Marhal.c documentation says “Some objects cannot be dumped: if the objects to be dumped include bindings, procedure objects, instances of class IO, or singleton objects, a TypeError will be raised.” None of the above seems to apply – even so we commented our all the model’s custom methods and attributes – effectively making it a vanilla ActiveRecord::Base object – to no effect.

Some sort of namespace collision was the prime suspect but we eliminated all likely possibilities.

Clearly somewhere along the line the Card class definition is getting unloaded. It may be something to do with Rails Engines, which we use heavily. I don’t really like relying on a mystery solution, but so far – it works.