memory_locker 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f3a6f1d81fcec94948578a19104e6465844402d3a58c2379b8c9a02e973fe8a
4
- data.tar.gz: 0c5133932a48f4409961bc2fc6beeb53c28b183f754497166ff401eabecc7c03
3
+ metadata.gz: cf1ecf9a93e9fd2ef3cf96d45489c2663704fba61001726fc362343cfaad5082
4
+ data.tar.gz: aafb584707726f4307a1bac47cd4095dae0d7a5bac7acd843cb31e895aa94db9
5
5
  SHA512:
6
- metadata.gz: 8f6c0e2f07e918f1971f7d0c7f1ddd5c8f82c42e438e796b5c829e46856371dbeaf7716c59f784541b7ee2f43e59648088ee7f65c4195d3df884fb192fe79f94
7
- data.tar.gz: 76494e83446a77fdc49b45c4c6ba51a59a1aafb64f238052c053b3bfcf8c80cb025a7dfddaa0bc9a1513a7730b2c2b13a5c371877dee53d2680fc406325f474a
6
+ metadata.gz: b0197269a2b177801c35ad9107ccedc6d45b01f5e2f91594909b081894d21833288573059c62e4195fe6e1ad4ba3dd501e6fb24545b6ed7219e424b7024a8f6b
7
+ data.tar.gz: fd22371427af738e0a4d52232f70bbc72f4f8be412fb655019ad858b91675afcaccb53b8fb915759a67f3728ced819c16edc1e810761d4c6ce4b2b1e6f6051ac
data/README.md CHANGED
@@ -1,54 +1,71 @@
1
1
  # MemoryLocker
2
+ [![Gem Version](https://badge.fury.io/rb/memory_locker.svg)](https://badge.fury.io/rb/memory_locker)
2
3
 
3
4
  Lock memory containing sensitive data (such as passwords or cryptographic keys) to prevent it from being swapped
4
5
  by the kernel, which allows the attacker with access to swap space to recover secrets.
5
6
 
6
- Ruby doesn't allow granular memory management, therefore the approach is to lock the entire memory of a program.
7
+ ## How it works
8
+
9
+ Ruby doesn't allow granular memory management, therefore the approach is to lock the entire memory of a current
10
+ process using `mlockall()`.
11
+
12
+ The memory will stay locked until the process terminates. Although unlocking memory is technically possible,
13
+ the gem doesn't allow it. The reason is Ruby doesn't support reliable removal of secrets from memory,
14
+ therefore it is safer to just keep memory locked.
15
+
16
+ Warning: if your app leaks memory, it won't be swapped and at some point may be killed by the kernel.
17
+
18
+ Subprocesses don't inherit memory locking. Make sure to lock memory in each one of them if they handle sensitive data.
7
19
 
8
20
  ## Requirements
9
21
 
10
- This gem requires `ffi` gem, which needs to be built on install.
22
+ This gem requires [ffi gem](https://github.com/ffi/ffi), which needs to be built on install.
11
23
  In case of build-related issues, make sure you have the compiler installed.
24
+ Refer to gem documentation for instructions.
12
25
 
13
- In Debian-based Linux distributions, you can install it by executing:
26
+ Also, an OS supporting `mlockall()` is required. Those include most Unixes except macOS. Windows is not supported.
14
27
 
15
- $ sudo apt install --no-install-recommends build-essential
28
+ ## Installation
16
29
 
17
- Refer to [ffi gem documentation](https://github.com/ffi/ffi) for requirements on other systems.
30
+ $ gem install memory_locker
18
31
 
19
- ## Installation
32
+ ## Usage
20
33
 
21
- Install the gem and add it to the application's Gemfile by executing:
34
+ ### Enforced installation
22
35
 
23
- $ bundle add memory_locker
36
+ To lock the memory of your process, add the following code early in the app lifetime:
24
37
 
25
- If the bundler is not being used to manage dependencies, install the gem by executing:
38
+ require 'memory_locker'
39
+ MemoryLocker.call
26
40
 
27
- $ gem install memory_locker
41
+ ### Optional installation
28
42
 
29
- ## Usage
43
+ If you don't want to force the user to install `memory_locker` gem, you can make it optional.
44
+ If the user doesn't have it installed, the warning will appear, but your app will run.
30
45
 
31
- To lock the memory of the current process use the following once, whenever you want,
32
- but before sensitive data processing:
46
+ begin
47
+ require 'memory_locker'
48
+ rescue LoadError
49
+ warn 'Failed to lock memory. To fix install `memory_locker` gem.'
50
+ else
51
+ MemoryLocker.call
52
+ end
33
53
 
34
- MemoryLocker.new(:glibc).lock!
54
+ ## Exceptions
35
55
 
36
- The above example uses the `glibc` backend which should work on most Linux distributions.
37
- Currently, only this backend is implemented, however, it is trivial to add support for other c-libraries.
56
+ If your OS is unsupported or there was a locking error, you will get an exception descending from MemoryLocker::Error.
38
57
 
39
- The memory will stay locked until the process terminates. There is no way to unlock memory.
40
- The reason is Ruby doesn't support reliable removal of secrets from memory, therefore it is safer to just
41
- keep memory locked.
58
+ ## Testing
59
+
60
+ Locking the memory of your app when testing is not needed, and if you use an unsupported OS will brake your app.
61
+
62
+ As only the `#call` method is being used, you can easily replace `MemoryLocker` with empty lambda `->{}`.
42
63
 
43
64
  ## Development
44
65
 
45
66
  After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
46
67
  You can also run `bin/console` for an interactive prompt that will allow you to experiment.
47
68
 
48
- To implement the backend for a different c-library, use existing one as a template.
49
- Copy `lib/memory_locker/glibc.rb` to `lib/memory_locker/my_backend.rb`, and change it.
50
- Later use `:my_backend` as an argument to `MemoryLocker` initializer.
51
-
52
69
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
53
70
  version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
54
71
  push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
@@ -65,5 +82,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
65
82
 
66
83
  ## Code of Conduct
67
84
 
68
- Everyone interacting in the MemoryLocker project's codebases, issue trackers, chat rooms, and mailing lists
69
- is expected to follow the [code of conduct](https://github.com/phantom-node/memory_locker/blob/master/CODE_OF_CONDUCT.md).
85
+ Everyone interacting in the MemoryLocker project's codebases, issue trackers, chat rooms, and mailing lists is
86
+ expected to follow the [code of conduct](https://github.com/phantom-node/memory_locker/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MemoryLocker
4
+ # Low level interface to libc
5
+ module Libc
6
+ # Those values should remain the same on all POSIX systems
7
+ MCL_CURRENT = 1
8
+ MCL_FUTURE = 2
9
+
10
+ extend FFI::Library
11
+ # Try to load already loaded libc from the current process, use system libc as a fallback
12
+ ffi_lib [FFI::CURRENT_PROCESS, FFI::Library::LIBC], FFI::Library::LIBC
13
+ attach_function :real_mlockall, :mlockall, [:int], :int
14
+
15
+ def self.mlockall
16
+ result = real_mlockall(MCL_CURRENT | MCL_FUTURE)
17
+ [result, FFI.errno]
18
+ end
19
+ end
20
+
21
+ private_constant :Libc
22
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MemoryLocker
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/memory_locker.rb CHANGED
@@ -7,17 +7,33 @@ require 'ffi'
7
7
  # It is implemented as a one-way operation: there is no unlock.
8
8
  # That's because it's hard to properly clean memory in Ruby.
9
9
  class MemoryLocker
10
- LockingError = Class.new StandardError
10
+ Error = Class.new StandardError
11
+ UnsupportedError = Class.new Error
12
+ LibcNotFoundError = Class.new Error
13
+ LockingError = Class.new Error
11
14
 
12
- def lock!
13
- Backend.lock! || raise(LockingError, "Failed to lock memory, errno #{FFI.errno}")
15
+ def call
16
+ result, errno = libc.mlockall
17
+ raise LockingError, "Locking of memory failed with errno #{errno}" unless result.zero?
18
+ end
19
+
20
+ def self.call
21
+ new.call
14
22
  end
15
23
 
16
24
  private
17
25
 
18
- attr_reader :backend
26
+ attr_reader :libc
19
27
 
20
- def initialize(backend)
21
- require_relative "memory_locker/#{backend}"
28
+ def initialize(libc_loader: -> { require_relative 'memory_locker/libc' },
29
+ libc_fetcher: -> { Libc },
30
+ unsupported_error: FFI::NotFoundError,
31
+ libc_not_found_error: LoadError)
32
+ libc_loader.call
33
+ @libc = libc_fetcher.call
34
+ rescue unsupported_error => e
35
+ raise UnsupportedError, 'System does not support mlockall()', cause: e
36
+ rescue libc_not_found_error => e
37
+ raise LibcNotFoundError, 'Failed to find C library', cause: e
22
38
  end
23
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory_locker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paweł Pokrywka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-27 00:00:00.000000000 Z
11
+ date: 2023-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -35,7 +35,7 @@ files:
35
35
  - LICENSE.txt
36
36
  - README.md
37
37
  - lib/memory_locker.rb
38
- - lib/memory_locker/glibc.rb
38
+ - lib/memory_locker/libc.rb
39
39
  - lib/memory_locker/version.rb
40
40
  homepage: https://github.com/phantom-node/memory_locker
41
41
  licenses:
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class MemoryLocker
4
- # Low level interface to glibc
5
- module Backend
6
- extend FFI::Library
7
- ffi_lib 'libc.so.6'
8
-
9
- MCL_CURRENT = 1
10
- MCL_FUTURE = 2
11
-
12
- attach_function :mlockall, [:int], :int
13
-
14
- def self.lock!
15
- mlockall(MCL_CURRENT | MCL_FUTURE).zero?
16
- end
17
- end
18
- end