ueno / ruby-gpgme

a ruby interface to GnuPG Made Easy (GPGME).
GNU Lesser General Public License v2.1
231 stars 99 forks source link

GPGME::Crypto#encrypt throws an Errno::EBADF exception #54

Open vakuum opened 10 years ago

vakuum commented 10 years ago

The following code always throws an Errno::EBADF exception after some loop iterations:

require 'gpgme'

puts GPGME::Engine.info.inspect

crypto = GPGME::Crypto.new(armor: false, symmetric: true, password: 'password')

(0..1000000).each do |n|
  puts n
  crypto.encrypt(GPGME::Data.from_str('data'))
end

I tried it with Ruby 2.0.0p353 and Ruby 2.0.0p576 on Ubuntu 12.04.5 LTS and Ubuntu 14.04.1 LTS:

$ rvm use ruby-2.0.0-p576@gpgme --create

$ gem install gpgme
...
Successfully installed mini_portile-0.6.0
...
Successfully installed gpgme-2.0.7
...

$ ruby test.rb
[#<GPGME::EngineInfo:0x000000014bda10 @protocol=0, @file_name="/usr/bin/gpg", @version="1.4.16", @req_version="1.4.0">, #<GPGME::EngineInfo:0x000000014bd970 @protocol=6, @file_name="/nonexistent", @version="1.0", @req_version="1.0">]
0
1
...
403
404
/home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:484:in `flush': Bad file descriptor (Errno::EBADF)
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:484:in `pass_function'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:449:in `call'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:449:in `gpgme_op_encrypt'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:449:in `encrypt'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/crypto.rb:99:in `block in encrypt'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/ctx.rb:71:in `new'
        from /home/clemens/.rvm/gems/ruby-2.0.0-p576@gpgme/gems/gpgme-2.0.7/lib/gpgme/crypto.rb:90:in `encrypt'
        from test.rb:21:in `block in <main>'
        from test.rb:19:in `each'
        from test.rb:19:in `<main>'

Installed packages under Ubuntu 12.04.5 LTS

$ dpkg --list gnupg libgpgme11 libgpgme11-dev
...
ii  gnupg                         1.4.11-3ubuntu2.7             GNU privacy guard - a free PGP replacement
ii  libgpgme11                    1.2.0-1.4ubuntu2.1            GPGME - GnuPG Made Easy
ii  libgpgme11-dev                1.2.0-1.4ubuntu2.1            GPGME - GnuPG Made Easy

Installed packages under Ubuntu 14.04.1 LTS

$ dpkg --list gnupg libgpgme11 libgpgme11-dev
...
ii  gnupg                         1.4.16-1ubuntu2.1   amd64               GNU privacy guard - a free PGP replacement
ii  libgpgme11:amd64              1.4.3-0.1ubuntu5.1  amd64               GPGME - GnuPG Made Easy (library)
ii  libgpgme11-dev                1.4.3-0.1ubuntu5.1  amd64               GPGME - GnuPG Made Easy (development files)
ueno commented 10 years ago

I'm still looking for a proper way to fix this, but for your use-case, a workaround would be to use a custom passphrase callback and call IO#close at the end:

def pass_function(pass, uid_hint, passphrase_info, prev_was_bad, fd)
  io = IO.for_fd(fd, 'w')
  io.puts 'password'
  io.flush
  io.close
end

crypto = GPGME::Crypto.new(armor: false, symmetric: true,
                           passphrase_callback: method(:pass_function))

However, I doubt that we can simply do that in our default callback, as other gpgme engines might assume the FD is never closed.

vakuum commented 10 years ago

Closing the file handle in the passphrase callback solved the problem.

Thank you!