memory_locker 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +12 -27
- data/lib/memory_locker/version.rb +1 -1
- data/lib/memory_locker.rb +29 -20
- metadata +3 -18
- data/lib/memory_locker/libc.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4bc016f2294342753deae3eda56b89e55b5345474f12603919b5063dc441045
|
|
4
|
+
data.tar.gz: ac399821325952ee575fcc226de9edc41c14139a078ae33eb1c7584f24033f54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a9ad9fc290ccbe7ce4fc0fe6a52d1bce11b0fb0899721c5e4e6492f14f54811f4f630faf632dc9e7b6afc944cc22a7b188d90b3dbd6065d79b98f17e5805944
|
|
7
|
+
data.tar.gz: d92c5be22a0b00041829053f32bc61a6c46ce63987062584956413da81832b44046dbe50b0d14e6c8bdc9b3a1c9b121b0f76612a9741c1ec65a985fa731a760a
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# MemoryLocker
|
|
2
|
+
|
|
2
3
|
[](https://badge.fury.io/rb/memory_locker)
|
|
3
4
|
|
|
4
5
|
Lock memory containing sensitive data (such as passwords or cryptographic keys) to prevent it from being swapped
|
|
@@ -7,7 +8,7 @@ by the kernel, which allows the attacker with access to swap space to recover se
|
|
|
7
8
|
## How it works
|
|
8
9
|
|
|
9
10
|
Ruby doesn't allow granular memory management, therefore the approach is to lock the entire memory of a current
|
|
10
|
-
process using
|
|
11
|
+
process using [mlockall()](https://linux.die.net/man/2/mlockall).
|
|
11
12
|
|
|
12
13
|
The memory will stay locked until the process terminates. Although unlocking memory is technically possible,
|
|
13
14
|
the gem doesn't allow it. The reason is Ruby doesn't support reliable removal of secrets from memory,
|
|
@@ -19,47 +20,31 @@ Subprocesses don't inherit memory locking. Make sure to lock memory in each one
|
|
|
19
20
|
|
|
20
21
|
## Requirements
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Refer to gem documentation for instructions.
|
|
25
|
-
|
|
26
|
-
Also, an OS supporting `mlockall()` is required. Those include most Unixes except macOS. Windows is not supported.
|
|
23
|
+
OS support for [mlockall()](https://linux.die.net/man/2/mlockall) is required.
|
|
24
|
+
Will work on most Unixes except macOS. Windows is not supported.
|
|
27
25
|
|
|
28
26
|
## Installation
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
gem install memory_locker
|
|
31
29
|
|
|
32
30
|
## Usage
|
|
33
31
|
|
|
34
|
-
### Enforced installation
|
|
35
|
-
|
|
36
32
|
To lock the memory of your process, add the following code early in the app lifetime:
|
|
37
33
|
|
|
38
34
|
require 'memory_locker'
|
|
39
|
-
MemoryLocker.call
|
|
40
|
-
|
|
41
|
-
### Optional installation
|
|
42
|
-
|
|
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.
|
|
45
|
-
|
|
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
|
|
35
|
+
MemoryLocker.call # short syntax
|
|
36
|
+
MemoryLocker.new.call # if you prefer to use instance explicitly
|
|
53
37
|
|
|
54
38
|
## Exceptions
|
|
55
39
|
|
|
56
|
-
If your OS is unsupported or there
|
|
40
|
+
If your OS is unsupported or there is a locking error, you will get an exception descending from `MemoryLocker::Error`.
|
|
57
41
|
|
|
58
42
|
## Testing
|
|
59
43
|
|
|
60
|
-
Locking the memory of your app when testing is not needed, and if you use an unsupported OS will
|
|
44
|
+
Locking the memory of your app when testing is not needed, and if you use an unsupported OS will break your app.
|
|
61
45
|
|
|
62
|
-
As only the `#call` method is being used, you can easily replace `MemoryLocker` with empty
|
|
46
|
+
As only the `#call` method is being used, you can easily replace `MemoryLocker` class or instance with an empty
|
|
47
|
+
lambda `->{}` in your tests.
|
|
63
48
|
|
|
64
49
|
## Development
|
|
65
50
|
|
|
@@ -72,7 +57,7 @@ push git commits and the created tag, and push the `.gem` file to [rubygems.org]
|
|
|
72
57
|
|
|
73
58
|
## Contributing
|
|
74
59
|
|
|
75
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/phantom-node/memory_locker
|
|
60
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/phantom-node/memory_locker>.
|
|
76
61
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
|
|
77
62
|
the [code of conduct](https://github.com/phantom-node/memory_locker/blob/master/CODE_OF_CONDUCT.md).
|
|
78
63
|
|
data/lib/memory_locker.rb
CHANGED
|
@@ -1,39 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require
|
|
3
|
+
require_relative "memory_locker/version"
|
|
4
|
+
require "fiddle"
|
|
5
5
|
|
|
6
6
|
# Lock process memory, so it won't be swapped by the kernel.
|
|
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
|
-
Error
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Error = Class.new StandardError
|
|
11
|
+
LockingError = Class.new Error
|
|
12
|
+
UnsupportedError = Class.new Error
|
|
13
|
+
|
|
14
|
+
# Those values should remain the same on all POSIX systems
|
|
15
|
+
MCL_CURRENT = 1
|
|
16
|
+
MCL_FUTURE = 2
|
|
17
|
+
private_constant :MCL_CURRENT, :MCL_FUTURE
|
|
14
18
|
|
|
15
19
|
def call
|
|
16
|
-
|
|
17
|
-
raise LockingError, "Locking of memory failed with errno #{errno}" unless result.zero?
|
|
20
|
+
raise LockingError, "Locking of memory failed" unless function.call(MCL_CURRENT | MCL_FUTURE).zero?
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def self.call
|
|
21
|
-
new.call
|
|
24
|
+
new.send :call
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
private
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
29
|
+
def function
|
|
30
|
+
lazy_function.call
|
|
34
31
|
rescue unsupported_error => e
|
|
35
|
-
raise UnsupportedError,
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
raise UnsupportedError, "Memory locking not supported: #{e.message}", cause: e
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :lazy_function, :unsupported_error
|
|
36
|
+
|
|
37
|
+
def initialize(
|
|
38
|
+
libc_path: nil,
|
|
39
|
+
function_name: "mlockall",
|
|
40
|
+
lazy_handle: -> { Fiddle.dlopen(libc_path)[function_name] },
|
|
41
|
+
lazy_function: -> { Fiddle::Function.new(lazy_handle.call, [Fiddle::TYPE_INT], Fiddle::TYPE_INT) },
|
|
42
|
+
unsupported_error: Fiddle::DLError
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@lazy_function = lazy_function
|
|
46
|
+
@unsupported_error = unsupported_error
|
|
38
47
|
end
|
|
39
48
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: memory_locker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
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-
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: ffi
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.0.0
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 1.0.0
|
|
11
|
+
date: 2023-11-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
27
13
|
description:
|
|
28
14
|
email:
|
|
29
15
|
- pepawel@users.noreply.github.com
|
|
@@ -35,7 +21,6 @@ files:
|
|
|
35
21
|
- LICENSE.txt
|
|
36
22
|
- README.md
|
|
37
23
|
- lib/memory_locker.rb
|
|
38
|
-
- lib/memory_locker/libc.rb
|
|
39
24
|
- lib/memory_locker/version.rb
|
|
40
25
|
homepage: https://github.com/phantom-node/memory_locker
|
|
41
26
|
licenses:
|
data/lib/memory_locker/libc.rb
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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]
|
|
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
|