parallel 2.0.1 → 2.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0556b829a420b259f2c4610988421e0b21516101042373b14cb9acfe870e9c8d
4
- data.tar.gz: 6c0a7aa158fb4a905902c1ef94e3901b75c73518754faa57a421cea4e82df6c7
3
+ metadata.gz: 3331bc6634cd376e4f3f8511049e49a0d4501110aaeac395e2f0c0b638f4a402
4
+ data.tar.gz: 357e1424cb8297b6472c2c5b9486e26da3969edd928be7d6ca7f21670211a8e0
5
5
  SHA512:
6
- metadata.gz: 76173acf1c6a08e53bf12cfe4c039b02ca5d891b7a0bfed7024a2ba0093d7cbe42790f6e2ac1ee8e195c99e3582c24fb2abb27c8882541361bdc794492df86f3
7
- data.tar.gz: a032ace70ba955047cd3d1bb73c85af8bf67664f41e2d61ccdc0d1298277754a8ce190e1f33d654507236a1208d532e7fe973cfe5abee55abd46f7b13b70be36
6
+ metadata.gz: 1885d4f814023905f76105f0edce9155024478408be851a4f6869e537400e924e03d34196e03b428803c274b5fbf7e9bf2d93f4548782aa6d3015063c7ed7883
7
+ data.tar.gz: 97d22f6b0320a089a3584e76c0baa245811861776e7337e913961cd64cfe0770131493d7de7e1355a62d6339d47d0b1f3ad32dd89084f31c69a61fcfab9f3148
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ require 'openssl'
3
+ require 'securerandom'
4
+
5
+ module Parallel
6
+ # Pluggable wire serializers. Each must respond to `dump(data, io)` /
7
+ # `load(io)` (used directly by Worker) and `dump(data)` / `load(string)`
8
+ # (used by wrappers like Hmac).
9
+ module Serializer
10
+ # Raw Marshal. Fast but trusts anything written to the pipe — a same-UID
11
+ # attacker that reopens /proc/<pid>/fd/<n> can inject Marshal gadgets (RCE).
12
+ Marshal = ::Marshal
13
+
14
+ # Wraps any inner serializer with a length-prefixed HMAC-SHA256 frame keyed
15
+ # on a per-worker secret generated before fork. Forged frames from a
16
+ # pipe-injector fail verification.
17
+ class Hmac
18
+ LENGTH_FORMAT = 'N' # 32-bit big-endian unsigned int
19
+ LENGTH_BYTES = 4
20
+ MAC_BYTES = 32 # SHA256
21
+
22
+ def initialize(inner: Marshal, secret: SecureRandom.bytes(32))
23
+ @inner = inner
24
+ @secret = secret
25
+ end
26
+
27
+ def dump(data, io)
28
+ payload = @inner.dump(data)
29
+ mac = OpenSSL::HMAC.digest('SHA256', @secret, payload)
30
+ io.write([payload.bytesize].pack(LENGTH_FORMAT), mac, payload)
31
+ end
32
+
33
+ def load(io)
34
+ # nil at frame boundary = clean EOF (worker died / pipe closed between messages)
35
+ header = io.read(LENGTH_BYTES) || raise(EOFError) # eof stops worker
36
+ raise SecurityError, "truncated frame header" if header.bytesize != LENGTH_BYTES
37
+
38
+ length = header.unpack1(LENGTH_FORMAT)
39
+ mac = io.read(MAC_BYTES)
40
+ raise SecurityError, "truncated frame mac" if mac.nil? || mac.bytesize != MAC_BYTES
41
+
42
+ payload = io.read(length)
43
+ raise SecurityError, "truncated frame payload" if payload.nil? || payload.bytesize != length
44
+
45
+ expected = OpenSSL::HMAC.digest('SHA256', @secret, payload)
46
+ raise SecurityError, "HMAC mismatch on worker pipe" unless OpenSSL.fixed_length_secure_compare(mac, expected)
47
+
48
+ @inner.load(payload)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Parallel
3
- VERSION = Version = '2.0.1' # rubocop:disable Naming/ConstantName
3
+ VERSION = Version = '2.1.0' # rubocop:disable Naming/ConstantName
4
4
  end
data/lib/parallel.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rbconfig'
3
3
  require 'parallel/version'
4
+ require 'parallel/serializer'
4
5
 
5
6
  module Parallel
6
7
  Stop = Object.new.freeze
@@ -63,10 +64,11 @@ module Parallel
63
64
  attr_reader :pid, :read, :write
64
65
  attr_accessor :thread
65
66
 
66
- def initialize(read, write, pid)
67
+ def initialize(read, write, pid, serializer)
67
68
  @read = read
68
69
  @write = write
69
70
  @pid = pid
71
+ @serializer = serializer
70
72
  end
71
73
 
72
74
  def stop
@@ -83,13 +85,13 @@ module Parallel
83
85
 
84
86
  def work(data)
85
87
  begin
86
- Marshal.dump(data, write)
88
+ @serializer.dump(data, write)
87
89
  rescue Errno::EPIPE
88
90
  raise DeadWorker
89
91
  end
90
92
 
91
93
  result = begin
92
- Marshal.load(read)
94
+ @serializer.load(read)
93
95
  rescue EOFError
94
96
  raise DeadWorker
95
97
  end
@@ -266,7 +268,7 @@ module Parallel
266
268
 
267
269
  if options[:in_processes] && options[:in_threads]
268
270
  raise ArgumentError, "Please specify only one of `in_processes` or `in_threads`."
269
- elsif RUBY_PLATFORM =~ /java/ && !options[:in_processes]
271
+ elsif RUBY_PLATFORM.include?('java') && !options[:in_processes]
270
272
  method = :in_threads
271
273
  size = options[method] || processor_count
272
274
  elsif options[:in_threads]
@@ -622,6 +624,7 @@ module Parallel
622
624
  def worker(job_factory, options, &block)
623
625
  child_read, parent_write = IO.pipe
624
626
  parent_read, child_write = IO.pipe
627
+ options[:serializer] ||= Serializer::Marshal
625
628
 
626
629
  pid = Process.fork do
627
630
  self.worker_number = options[:worker_number]
@@ -642,12 +645,13 @@ module Parallel
642
645
  child_read.close
643
646
  child_write.close
644
647
 
645
- Worker.new(parent_read, parent_write, pid)
648
+ Worker.new(parent_read, parent_write, pid, options[:serializer])
646
649
  end
647
650
 
648
651
  def process_incoming_jobs(read, write, job_factory, options, &block)
652
+ serializer = options.fetch(:serializer)
649
653
  until read.eof?
650
- data = Marshal.load(read)
654
+ data = serializer.load(read)
651
655
  item, index = job_factory.unpack(data)
652
656
 
653
657
  result =
@@ -661,7 +665,7 @@ module Parallel
661
665
  end
662
666
 
663
667
  begin
664
- Marshal.dump(result, write)
668
+ serializer.dump(result, write)
665
669
  rescue Errno::EPIPE
666
670
  return # parent thread already dead
667
671
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -16,16 +16,17 @@ extra_rdoc_files: []
16
16
  files:
17
17
  - MIT-LICENSE.txt
18
18
  - lib/parallel.rb
19
+ - lib/parallel/serializer.rb
19
20
  - lib/parallel/version.rb
20
21
  homepage: https://github.com/grosser/parallel
21
22
  licenses:
22
23
  - MIT
23
24
  metadata:
24
25
  bug_tracker_uri: https://github.com/grosser/parallel/issues
25
- documentation_uri: https://github.com/grosser/parallel/blob/v2.0.1/Readme.md
26
- source_code_uri: https://github.com/grosser/parallel/tree/v2.0.1
26
+ documentation_uri: https://github.com/grosser/parallel/blob/v2.1.0/Readme.md
27
+ source_code_uri: https://github.com/grosser/parallel/tree/v2.1.0
27
28
  wiki_uri: https://github.com/grosser/parallel/wiki
28
- changelog_uri: https://github.com/grosser/parallel/blob/v2.0.1/CHANGELOG.md
29
+ changelog_uri: https://github.com/grosser/parallel/blob/v2.1.0/CHANGELOG.md
29
30
  rubygems_mfa_required: 'true'
30
31
  rdoc_options: []
31
32
  require_paths: