diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a84e4e..6e01a0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,3 +46,18 @@ jobs: bundler-cache: true - run: bundle exec rake compile - run: bundle exec rake test + + memcheck: + name: Memory leak check (valgrind) + runs-on: ubuntu-latest + env: + BUNDLE_GEMFILE: Gemfile.memcheck + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + - run: sudo apt-get install -y valgrind + - run: bundle exec rake compile + - run: bundle exec rake memcheck diff --git a/Gemfile.memcheck b/Gemfile.memcheck new file mode 100644 index 0000000..e5b4751 --- /dev/null +++ b/Gemfile.memcheck @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gemspec + +gem 'ruby_memcheck' diff --git a/README.md b/README.md index 1e771ca..6ad7822 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ The C sources are synced from openssh-portable, which tracks OpenBSD CVS: |------|----------------| | `ext/mri/bcrypt_pbkdf.c` | [v1.17](https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c?rev=1.17) | | `ext/mri/blowfish.c` | [v1.20](https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libcrypto/blowfish.c?rev=1.20) | +| `ext/mri/blf.h` | [v1.8](https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libcrypto/blf.h?rev=1.8) | ## Links diff --git a/Rakefile b/Rakefile index 950fccd..907e8d6 100644 --- a/Rakefile +++ b/Rakefile @@ -7,6 +7,15 @@ require 'rdoc/task' require 'benchmark' require 'rake_compiler_dock' +begin + require 'ruby_memcheck' + RubyMemcheck::TestTask.new(memcheck: :compile) do |t| + t.libs << 'test' + t.test_files = FileList['test/**/*_test.rb'] + end +rescue LoadError +end + CLEAN.add("{ext,lib}/**/*.{o,so}", "pkg") cross_rubies = ["3.4.0", "3.3.0", "3.2.0", "3.1.0", "3.0.0", "2.7.0"] diff --git a/ext/mri/bcrypt_pbkdf_ext.c b/ext/mri/bcrypt_pbkdf_ext.c index afa06dd..df94a37 100644 --- a/ext/mri/bcrypt_pbkdf_ext.c +++ b/ext/mri/bcrypt_pbkdf_ext.c @@ -8,6 +8,8 @@ static VALUE cBCryptPbkdfEngine; */ static VALUE bc_crypt_pbkdf(VALUE self, VALUE pass, VALUE salt, VALUE keylen, VALUE rounds) { size_t okeylen = NUM2ULONG(keylen); + if (okeylen == 0 || okeylen > 1024) + return Qnil; u_int8_t* okey = xmalloc(okeylen); VALUE out; @@ -16,8 +18,10 @@ static VALUE bc_crypt_pbkdf(VALUE self, VALUE pass, VALUE salt, VALUE keylen, VA (const u_int8_t*)StringValuePtr(salt), RSTRING_LEN(salt), okey, okeylen, NUM2ULONG(rounds)); - if (ret < 0) + if (ret < 0) { + xfree(okey); return Qnil; + } out = rb_str_new((const char*)okey, okeylen); xfree(okey); return out; diff --git a/ext/mri/blf.h b/ext/mri/blf.h index cd65d7e..54f5178 100644 --- a/ext/mri/blf.h +++ b/ext/mri/blf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ /* * Blowfish - a fast block cipher designed by Bruce Schneier * @@ -13,10 +13,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products + * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR diff --git a/test/bcrypt_pnkdf/engine_test.rb b/test/bcrypt_pnkdf/engine_test.rb index 2d81552..eaaa6cb 100644 --- a/test/bcrypt_pnkdf/engine_test.rb +++ b/test/bcrypt_pnkdf/engine_test.rb @@ -66,6 +66,21 @@ def test_ruby_and_native_returns_the_same end + # Issue #31/33: xmalloc(okeylen) was called before the guards inside + # bcrypt_pbkdf(), so out-of-range keylen caused a heap allocation that was + # never freed when bcrypt_pbkdf() returned -1. `loop { key("p","s",2000,1) }` + # on unfixed code would exhaust memory. + def test_invalid_keylen_returns_nil + assert_nil BCryptPbkdf::Engine.__bc_crypt_pbkdf("pass", "salt", 0, 1) + assert_nil BCryptPbkdf::Engine.__bc_crypt_pbkdf("pass", "salt", 1025, 1) + assert_nil BCryptPbkdf::Engine.__bc_crypt_pbkdf("pass", "salt", 2000, 1) + assert_nil BCryptPbkdf::Engine.__bc_crypt_pbkdf("pass", "salt", 32, 0) + end + + def test_invalid_keylen_does_not_leak_memory + 1000.times { BCryptPbkdf::Engine.__bc_crypt_pbkdf("pass", "salt", 2000, 1) } + end + def table [ ["pass2", "salt2", 12, 2, [214, 14, 48, 162, 131, 206, 121, 176, 50, 104, 231, 252]],