Notes to self

Patching gems for security vulnerabilities with gem-patch

gem-patch is a RubyGems plugin that helps you to patch gems. You can use it to apply security fixes or cherry-pick commits you want to apply to your .gem files. I use it to test whether the upstream commits containing vulnerability fixes apply cleanly on older gem releases so I can prepare fixed builds of those gems in Fedora. Here is how one can do that.

As an example let’s look on recent upstream patches for CVE-2015-3226 and CVE-2015-3227. The patches from upstream are commits for ActiveSupport version 4.2.1 (the latest release in a given supported branch). That make sense. But you might be on different version and you might be in a position when updating all related Rails gems is not a way to go. That’s also the case of Fedora 22 which features ActiveSupport 4.2.0.

The original purpose of gem-patch is to create (or apply) patches with ease. Let’s look how I test whether the released patches work with 4.2.0:

$ gem patch activesupport-4.2.0.gem -c./test rubygem-activesupport-4.2.2-CVE-2015-3226.patch \
 -p2 --dry-run --verbose
  • -c means copy-in. I need to include the ActiveSupport tests next to the unpacked gem sources, because the upstream gem files don’t contain those and I need to make sure that the tests are properly patched as well (they are run in our RPM builds).
  • -p2 is an patch utility argument which is used by gem-patch underneath. We specify -p2 because Rails team releases patches in form of commits for the whole Rails repository (including all associated gems), but we actually need patches just for ActiveSupport (starting with lib/ path).
  • –dry-run makes sure we don’t actually override the given .gem file. Perfect for testing. The nice thing about gem-patch’s --dry-run is that it applies the fixes on the .gem file copies rather than passing the option to the patch program (which has some limitations if you have two patches both changing the same file).
  • –verbose is used to see the output of the patch command. I need this since I don’t just care about the fix being applied, but also about it being applied cleanly (without offsets).

Here is a partial output:

Applying patch rubygem-activesupport-4.2.2-CVE-2015-3226.patch
Path to the patch to apply: /home/strzibny/fedora/rubygem-activesupport/rubygem-activesupport-4.2.2-CVE-2015-3226.patch
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|From ca91e927557906592f39ad5c07da25eefa9d8e61 Mon Sep 17 00:00:00 2001
|From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?=
| <rafaelmfranca@gmail.com>
|Date: Mon, 15 Jun 2015 15:23:01 -0300
|Subject: [PATCH] Escape HTML entities in JSON keys
|
|Fixes CVE-2015-3226
|---
| activesupport/lib/active_support/json/encoding.rb | 4 ++++
| activesupport/test/json/encoding_test.rb          | 7 +++++++
| 2 files changed, 11 insertions(+)
|
|diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
|index c0ac5af..fdd63d9 100644
|--- a/activesupport/lib/active_support/json/encoding.rb
|+++ b/activesupport/lib/active_support/json/encoding.rb
--------------------------
patching file lib/active_support/json/encoding.rb
Using Plan A...
Hunk #1 succeeded at 58.
Hmm...  The next patch looks like a unified diff to me...
The text leading up to this was:
--------------------------
|diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
|index 7e976aa..0cbe2da 100644
|--- a/activesupport/test/json/encoding_test.rb
|+++ b/activesupport/test/json/encoding_test.rb
--------------------------
patching file test/json/encoding_test.rb
Using Plan A...
Hunk #1 succeeded at 143 (offset -3 lines).
Hmm...  Ignoring the trailing garbage.
done
  Successfully built RubyGem
  Name: activesupport
  Version: 4.2.0
  File: activesupport-4.2.0.gem
Succesfully patched with rubygem-activesupport-4.2.2-CVE-2015-3226.patch

Nice, it works. And we also see how to apply the patch cleanly (which is important if you want to make sure that any additional files are created by patch program). For that we just change the line 146 to 143:

From ca91e927557906592f39ad5c07da25eefa9d8e61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?=
 <rafaelmfranca@gmail.com>
Date: Mon, 15 Jun 2015 15:23:01 -0300
Subject: [PATCH] Escape HTML entities in JSON keys

Fixes CVE-2015-3226
---
 activesupport/lib/active_support/json/encoding.rb | 4 ++++
 activesupport/test/json/encoding_test.rb          | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index c0ac5af..fdd63d9 100644

...

diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 7e976aa..0cbe2da 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -143,6 +143,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
     assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
   end

+  def test_hash_keys_encoding
+    ActiveSupport.escape_html_entities_in_json = true
+    assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
+  ensure
+    ActiveSupport.escape_html_entities_in_json = false
+  end
+
   def test_utf8_string_encoded_properly
     result = ActiveSupport::JSON.encode('€2.99')
     assert_equal '"€2.99"', result
--

We can run gem patch again and see that the patch applied cleanly.

...
|diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
|index 7e976aa..0cbe2da 100644
|--- a/activesupport/test/json/encoding_test.rb
|+++ b/activesupport/test/json/encoding_test.rb
--------------------------
patching file test/json/encoding_test.rb
Using Plan A...
Hunk #1 succeeded at 143.
Hmm...  Ignoring the trailing garbage.
done
  Successfully built RubyGem
  Name: activesupport
  Version: 4.2.0
  File: activesupport-4.2.0.gem
Succesfully patched with rubygem-activesupport-4.2.2-CVE-2015-3226.patch

But there was one more vulnerability reported in the announcement. To apply both at the same time we just add it as another argument:

$ gem patch activesupport-4.2.0.gem -ctest -p2 --dry-run --verbose \
  rubygem-activesupport-4.2.2-CVE-2015-3226.patch rubygem-activesupport-4.2.2-CVE-2015-3227.patch

That’s it. We can remove the --dry-run option now if we want to patch the given .gem file or include our fixed patches in the build process (specfiles in case of Fedora builds).

Work with me

I have some availability for contract work. I can be your fractional CTO, a Ruby on Rails engineer, or consultant. Write me at strzibny@strzibny.name.

RSS