seccomp-tools 1.6.2 → 1.7.1

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -0
  3. data/README.md +280 -42
  4. data/completions/_seccomp-tools +83 -0
  5. data/completions/seccomp-tools.bash +59 -0
  6. data/completions/seccomp-tools.fish +52 -0
  7. data/ext/ptrace/ptrace.c +2 -2
  8. data/lib/seccomp-tools/asm/asm.rb +9 -4
  9. data/lib/seccomp-tools/asm/compiler.rb +32 -2
  10. data/lib/seccomp-tools/asm/sasm.tab.rb +27 -19
  11. data/lib/seccomp-tools/asm/sasm.y +15 -7
  12. data/lib/seccomp-tools/asm/scalar.rb +50 -7
  13. data/lib/seccomp-tools/asm/scanner.rb +33 -1
  14. data/lib/seccomp-tools/asm/statement.rb +14 -5
  15. data/lib/seccomp-tools/asm/token.rb +19 -1
  16. data/lib/seccomp-tools/audit/catalog.rb +69 -0
  17. data/lib/seccomp-tools/audit/checks/arch_unchecked.rb +41 -0
  18. data/lib/seccomp-tools/audit/checks/dangerous_allow.rb +34 -0
  19. data/lib/seccomp-tools/audit/checks/orw_chain.rb +41 -0
  20. data/lib/seccomp-tools/audit/checks/permissive_default.rb +29 -0
  21. data/lib/seccomp-tools/audit/checks/syscall_alt_gap.rb +44 -0
  22. data/lib/seccomp-tools/audit/checks/x32_guard.rb +46 -0
  23. data/lib/seccomp-tools/audit/checks.rb +42 -0
  24. data/lib/seccomp-tools/audit/finding.rb +25 -0
  25. data/lib/seccomp-tools/audit/policy.rb +126 -0
  26. data/lib/seccomp-tools/audit/report.rb +98 -0
  27. data/lib/seccomp-tools/audit.rb +48 -0
  28. data/lib/seccomp-tools/bpf.rb +27 -17
  29. data/lib/seccomp-tools/cli/asm.rb +6 -2
  30. data/lib/seccomp-tools/cli/audit.rb +86 -0
  31. data/lib/seccomp-tools/cli/base.rb +51 -8
  32. data/lib/seccomp-tools/cli/cli.rb +9 -3
  33. data/lib/seccomp-tools/cli/completion.rb +40 -0
  34. data/lib/seccomp-tools/cli/disasm.rb +9 -5
  35. data/lib/seccomp-tools/cli/dump.rb +37 -52
  36. data/lib/seccomp-tools/cli/dumpable.rb +79 -0
  37. data/lib/seccomp-tools/cli/emu.rb +20 -5
  38. data/lib/seccomp-tools/cli/explain.rb +51 -0
  39. data/lib/seccomp-tools/cli/filter_input.rb +130 -0
  40. data/lib/seccomp-tools/const.rb +98 -15
  41. data/lib/seccomp-tools/consts/sys_arg.rb +43 -5
  42. data/lib/seccomp-tools/consts/sys_nr/aarch64.rb +52 -1
  43. data/lib/seccomp-tools/consts/sys_nr/amd64.rb +53 -1
  44. data/lib/seccomp-tools/consts/sys_nr/i386.rb +83 -2
  45. data/lib/seccomp-tools/consts/sys_nr/riscv64.rb +332 -0
  46. data/lib/seccomp-tools/consts/sys_nr/s390x.rb +30 -1
  47. data/lib/seccomp-tools/disasm/disasm.rb +30 -12
  48. data/lib/seccomp-tools/dumper.rb +67 -36
  49. data/lib/seccomp-tools/emulator.rb +40 -15
  50. data/lib/seccomp-tools/error.rb +4 -2
  51. data/lib/seccomp-tools/explain/analysis.rb +67 -0
  52. data/lib/seccomp-tools/explain/path_facts.rb +110 -0
  53. data/lib/seccomp-tools/explain/qword.rb +204 -0
  54. data/lib/seccomp-tools/explain/renderer.rb +128 -0
  55. data/lib/seccomp-tools/explain/summary.rb +218 -0
  56. data/lib/seccomp-tools/explain/verdict.rb +44 -0
  57. data/lib/seccomp-tools/explain.rb +38 -0
  58. data/lib/seccomp-tools/instruction/alu.rb +14 -9
  59. data/lib/seccomp-tools/instruction/base.rb +39 -10
  60. data/lib/seccomp-tools/instruction/jmp.rb +50 -23
  61. data/lib/seccomp-tools/instruction/ld.rb +46 -21
  62. data/lib/seccomp-tools/instruction/ldx.rb +4 -3
  63. data/lib/seccomp-tools/instruction/misc.rb +11 -9
  64. data/lib/seccomp-tools/instruction/ret.rb +12 -6
  65. data/lib/seccomp-tools/instruction/st.rb +15 -6
  66. data/lib/seccomp-tools/instruction/stx.rb +4 -3
  67. data/lib/seccomp-tools/logger.rb +14 -1
  68. data/lib/seccomp-tools/symbolic/constraint.rb +80 -0
  69. data/lib/seccomp-tools/symbolic/executor.rb +210 -0
  70. data/lib/seccomp-tools/symbolic/expr.rb +185 -0
  71. data/lib/seccomp-tools/symbolic/state.rb +82 -0
  72. data/lib/seccomp-tools/syscall.rb +73 -23
  73. data/lib/seccomp-tools/util.rb +76 -11
  74. data/lib/seccomp-tools/version.rb +1 -1
  75. data/lib/seccomp-tools.rb +10 -1
  76. metadata +36 -24
  77. data/lib/seccomp-tools/disasm/context.rb +0 -171
@@ -1,38 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SeccompTools
4
- # Define utility methods.
4
+ # Utility methods shared across the library: architecture detection and terminal colorizing.
5
5
  module Util
6
6
  module_function
7
7
 
8
8
  # Get currently supported architectures.
9
+ #
10
+ # Derived from the syscall tables shipped under +consts/sys_nr/+.
9
11
  # @return [Array<Symbol>]
10
- # Architectures.
12
+ # Architecture names, sorted.
11
13
  def supported_archs
12
14
  @supported_archs ||= Dir.glob(File.join(__dir__, 'consts', 'sys_nr', '*.rb'))
13
15
  .map { |f| File.basename(f, '.rb').to_sym }
14
16
  .sort
15
17
  end
16
18
 
19
+ # Whether the host operating system is Linux, where the +ptrace+-based dumping works.
20
+ # @return [Boolean]
21
+ def linux?
22
+ RbConfig::CONFIG['host_os'].include?('linux')
23
+ end
24
+
17
25
  # Detect system architecture.
18
26
  # @return [Symbol]
27
+ # One of {supported_archs}, or +:unknown+ if the host CPU is not supported.
19
28
  def system_arch
20
29
  case RbConfig::CONFIG['host_cpu']
21
30
  when /x86_64/ then :amd64
22
31
  when /i386/ then :i386
23
32
  when /aarch64/ then :aarch64
33
+ when /riscv64/ then :riscv64
24
34
  when /s390x/ then :s390x
25
35
  else :unknown
26
36
  end
27
37
  end
28
38
 
39
+ # ELF +e_machine+ values (the halfword at offset 18) of the architectures seccomp-tools supports,
40
+ # as they are laid out in the file - so the big-endian s390x reads the other way around. The keys
41
+ # are binary strings (+.b+) to match what is read off the file: this source is UTF-8, where a
42
+ # literal such as +"\xb7\x00"+ would never compare equal to those bytes.
43
+ ELF_MACHINE = {
44
+ "\x03\x00".b => :i386,
45
+ "\x3e\x00".b => :amd64,
46
+ "\xb7\x00".b => :aarch64,
47
+ "\xf3\x00".b => :riscv64,
48
+ "\x00\x16".b => :s390x
49
+ }.freeze
50
+
51
+ # The architecture a running process was built for, read from the ELF header of its executable.
52
+ #
53
+ # Filters dumped from another process are numbered for *its* architecture, so this is what makes
54
+ # the syscall names right when it differs from the host.
55
+ # @param [Integer] pid
56
+ # Process identifier.
57
+ # @return [Symbol?]
58
+ # One of {supported_archs}, or +nil+ when the executable cannot be read (no +/proc+, the
59
+ # process is gone, permission denied) or its machine type is not one we know.
60
+ def process_arch(pid)
61
+ File.open("/proc/#{pid}/exe", 'rb') do |f|
62
+ f.pos = 18
63
+ ELF_MACHINE[f.read(2)]
64
+ end
65
+ rescue SystemCallError
66
+ nil
67
+ end
68
+
29
69
  # Enable colorize.
70
+ #
71
+ # Colors are still only emitted when the output is a tty, see {colorize_enabled?}.
30
72
  # @return [void]
31
73
  def enable_color!
32
74
  @disable_color = false
33
75
  end
34
76
 
35
- # Disable colorize.
77
+ # Disable colorize, {colorize} becomes a no-op regardless of the output being a tty.
36
78
  # @return [void]
37
79
  def disable_color!
38
80
  @disable_color = true
@@ -40,28 +82,35 @@ module SeccompTools
40
82
 
41
83
  # Is colorize enabled?
42
84
  # @return [Boolean]
85
+ # +true+ only if colors have not been disabled by {disable_color!} and +$stdout+ is a tty.
43
86
  def colorize_enabled?
44
87
  !@disable_color && $stdout.tty?
45
88
  end
46
89
 
47
90
  # color code of light yellow
48
91
  LIGHT_YELLOW = "\e[38;5;230m"
49
- # Color codes for pretty print.
92
+ # Color codes for pretty print. +error+, +warn+ and +info+ are severities rather than parts of a
93
+ # filter, shading from alarming to quiet; {SeccompTools::Logger} colors its own levels by the
94
+ # same names.
50
95
  COLOR_CODE = {
51
96
  esc_m: "\e[0m",
52
97
  syscall: "\e[38;5;120m", # light green
53
98
  arch: LIGHT_YELLOW,
54
99
  args: LIGHT_YELLOW,
55
100
  gray: "\e[2m",
56
- error: "\e[38;5;196m" # heavy red
101
+ error: "\e[38;5;196m", # heavy red
102
+ warn: LIGHT_YELLOW,
103
+ info: "\e[38;5;110m" # light blue
57
104
  }.freeze
58
- # Wrapper color codes.
59
- # @param [String] s
60
- # Contents to wrapper.
105
+ # Wrap contents with terminal color codes.
106
+ #
107
+ # Returns +s+ unchanged when {colorize_enabled?} is +false+.
108
+ # @param [#to_s] s
109
+ # Contents to be wrapped.
61
110
  # @param [Symbol?] t
62
- # Specific which kind of color to use, valid symbols are defined in {Util.COLOR_CODE}.
111
+ # Which kind of color to use, valid symbols are the keys of {Util::COLOR_CODE}.
63
112
  # @return [String]
64
- # Wrapper with color codes.
113
+ # +s+ wrapped with color codes.
65
114
  def colorize(s, t: nil)
66
115
  s = s.to_s
67
116
  return s unless colorize_enabled?
@@ -71,13 +120,29 @@ module SeccompTools
71
120
  "#{color}#{s.sub(cc[:esc_m], cc[:esc_m] + color)}#{cc[:esc_m]}"
72
121
  end
73
122
 
123
+ # Does the file at +path+ start with the ELF magic bytes?
124
+ #
125
+ # Used to tell an executable apart from a raw BPF blob.
126
+ # @param [String] path
127
+ # Path to the file to check.
128
+ # @return [Boolean]
129
+ # +false+ when the file cannot be read.
130
+ def elf?(path)
131
+ File.binread(path, 4) == "\x7fELF".b
132
+ rescue SystemCallError
133
+ false
134
+ end
135
+
74
136
  # Get content of filename under directory templates/.
75
137
  #
76
138
  # @param [String] filename
77
- # The filename.
139
+ # Basename of a file under +lib/seccomp-tools/templates/+.
78
140
  #
79
141
  # @return [String]
80
142
  # Content of the file.
143
+ #
144
+ # @raise [Errno::ENOENT]
145
+ # If no such template exists.
81
146
  def template(filename)
82
147
  File.binread(File.join(__dir__, 'templates', filename))
83
148
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module SeccompTools
4
4
  # Gem version.
5
- VERSION = '1.6.2'
5
+ VERSION = '1.7.1'
6
6
  end
data/lib/seccomp-tools.rb CHANGED
@@ -2,7 +2,16 @@
2
2
 
3
3
  # @author david942j
4
4
 
5
- # Main module.
5
+ # Toolkit for working with seccomp BPF filters.
6
+ #
7
+ # The library entry points are {Asm.asm} to compile assembly into raw BPF, {Disasm.disasm} to turn
8
+ # raw BPF back into readable assembly, {Dumper.dump} to capture the filters a process installs, and
9
+ # {Emulator} to run a filter against a hypothetical syscall.
10
+ #
11
+ # @example
12
+ # raw = SeccompTools::Asm.asm("return ALLOW\n", arch: :amd64)
13
+ # SeccompTools::Disasm.disasm(raw, arch: :amd64, display_bpf: false)
14
+ # #=> "0000: return ALLOW\n"
6
15
  module SeccompTools
7
16
  end
8
17
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seccomp-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
@@ -85,14 +85,14 @@ dependencies:
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.21'
88
+ version: '1.0'
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '0.21'
95
+ version: '1.0'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: yard
98
98
  requirement: !ruby/object:Gem::Requirement
@@ -121,26 +121,6 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
- - !ruby/object:Gem::Dependency
125
- name: os
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '1.1'
131
- - - ">="
132
- - !ruby/object:Gem::Version
133
- version: 1.1.1
134
- type: :runtime
135
- prerelease: false
136
- version_requirements: !ruby/object:Gem::Requirement
137
- requirements:
138
- - - "~>"
139
- - !ruby/object:Gem::Version
140
- version: '1.1'
141
- - - ">="
142
- - !ruby/object:Gem::Version
143
- version: 1.1.1
144
124
  - !ruby/object:Gem::Dependency
145
125
  name: racc
146
126
  requirement: !ruby/object:Gem::Requirement
@@ -166,9 +146,13 @@ extensions:
166
146
  - ext/ptrace/extconf.rb
167
147
  extra_rdoc_files: []
168
148
  files:
149
+ - CHANGELOG.md
169
150
  - LICENSE
170
151
  - README.md
171
152
  - bin/seccomp-tools
153
+ - completions/_seccomp-tools
154
+ - completions/seccomp-tools.bash
155
+ - completions/seccomp-tools.fish
172
156
  - ext/ptrace/extconf.rb
173
157
  - ext/ptrace/ptrace.c
174
158
  - lib/seccomp-tools.rb
@@ -180,24 +164,48 @@ files:
180
164
  - lib/seccomp-tools/asm/scanner.rb
181
165
  - lib/seccomp-tools/asm/statement.rb
182
166
  - lib/seccomp-tools/asm/token.rb
167
+ - lib/seccomp-tools/audit.rb
168
+ - lib/seccomp-tools/audit/catalog.rb
169
+ - lib/seccomp-tools/audit/checks.rb
170
+ - lib/seccomp-tools/audit/checks/arch_unchecked.rb
171
+ - lib/seccomp-tools/audit/checks/dangerous_allow.rb
172
+ - lib/seccomp-tools/audit/checks/orw_chain.rb
173
+ - lib/seccomp-tools/audit/checks/permissive_default.rb
174
+ - lib/seccomp-tools/audit/checks/syscall_alt_gap.rb
175
+ - lib/seccomp-tools/audit/checks/x32_guard.rb
176
+ - lib/seccomp-tools/audit/finding.rb
177
+ - lib/seccomp-tools/audit/policy.rb
178
+ - lib/seccomp-tools/audit/report.rb
183
179
  - lib/seccomp-tools/bpf.rb
184
180
  - lib/seccomp-tools/cli/asm.rb
181
+ - lib/seccomp-tools/cli/audit.rb
185
182
  - lib/seccomp-tools/cli/base.rb
186
183
  - lib/seccomp-tools/cli/cli.rb
184
+ - lib/seccomp-tools/cli/completion.rb
187
185
  - lib/seccomp-tools/cli/disasm.rb
188
186
  - lib/seccomp-tools/cli/dump.rb
187
+ - lib/seccomp-tools/cli/dumpable.rb
189
188
  - lib/seccomp-tools/cli/emu.rb
189
+ - lib/seccomp-tools/cli/explain.rb
190
+ - lib/seccomp-tools/cli/filter_input.rb
190
191
  - lib/seccomp-tools/const.rb
191
192
  - lib/seccomp-tools/consts/sys_arg.rb
192
193
  - lib/seccomp-tools/consts/sys_nr/aarch64.rb
193
194
  - lib/seccomp-tools/consts/sys_nr/amd64.rb
194
195
  - lib/seccomp-tools/consts/sys_nr/i386.rb
196
+ - lib/seccomp-tools/consts/sys_nr/riscv64.rb
195
197
  - lib/seccomp-tools/consts/sys_nr/s390x.rb
196
- - lib/seccomp-tools/disasm/context.rb
197
198
  - lib/seccomp-tools/disasm/disasm.rb
198
199
  - lib/seccomp-tools/dumper.rb
199
200
  - lib/seccomp-tools/emulator.rb
200
201
  - lib/seccomp-tools/error.rb
202
+ - lib/seccomp-tools/explain.rb
203
+ - lib/seccomp-tools/explain/analysis.rb
204
+ - lib/seccomp-tools/explain/path_facts.rb
205
+ - lib/seccomp-tools/explain/qword.rb
206
+ - lib/seccomp-tools/explain/renderer.rb
207
+ - lib/seccomp-tools/explain/summary.rb
208
+ - lib/seccomp-tools/explain/verdict.rb
201
209
  - lib/seccomp-tools/instruction/alu.rb
202
210
  - lib/seccomp-tools/instruction/base.rb
203
211
  - lib/seccomp-tools/instruction/instruction.rb
@@ -209,6 +217,10 @@ files:
209
217
  - lib/seccomp-tools/instruction/st.rb
210
218
  - lib/seccomp-tools/instruction/stx.rb
211
219
  - lib/seccomp-tools/logger.rb
220
+ - lib/seccomp-tools/symbolic/constraint.rb
221
+ - lib/seccomp-tools/symbolic/executor.rb
222
+ - lib/seccomp-tools/symbolic/expr.rb
223
+ - lib/seccomp-tools/symbolic/state.rb
212
224
  - lib/seccomp-tools/syscall.rb
213
225
  - lib/seccomp-tools/templates/asm.amd64.asm
214
226
  - lib/seccomp-tools/templates/asm.c
@@ -1,171 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SeccompTools
4
- module Disasm
5
- # @private
6
- #
7
- # Context for disassembler to analyze.
8
- #
9
- # This class maintains:
10
- # * if +reg/mem+ can be one of +data[*]+
11
- # * if +data[0]+ (i.e. sys_number) is a known value
12
- class Context
13
- # @private
14
- #
15
- # Records the type and value.
16
- class Value
17
- attr_reader :val # @return [Integer]
18
-
19
- # @param [:imm, :data, :mem] rel
20
- # @param [Integer?] val
21
- def initialize(rel: :imm, val: nil)
22
- @rel = rel
23
- @val = val
24
- end
25
-
26
- # @return [Boolean]
27
- def data?
28
- @rel == :data
29
- end
30
-
31
- # @return [Boolean]
32
- def imm?
33
- @rel == :imm && @val.is_a?(Integer)
34
- end
35
-
36
- # Defines hash function.
37
- # @return [Integer]
38
- def hash
39
- [@rel, @val].hash
40
- end
41
-
42
- # Defines +eql?+.
43
- #
44
- # @param [Context::Value] other
45
- # @return [Boolean]
46
- def eql?(other)
47
- @val == other.val && @rel == other.instance_variable_get(:@rel)
48
- end
49
- end
50
-
51
- # @return [{Integer, Symbol => Context::Value}] Records reg and mem values.
52
- attr_reader :values
53
- # @return [Array<Integer?>] Records the known value of data.
54
- attr_reader :known_data
55
-
56
- # Instantiate a {Context} object.
57
- # @param [{Integer, Symbol => Context::Value?}] values
58
- # Value to be set to +reg/mem+.
59
- # @param [Array<Integer?>] known_data
60
- # Records which index of data is known.
61
- # It's used for tracking when the syscall number is known, which can be used to display argument names of the
62
- # syscall.
63
- def initialize(values: {}, known_data: [])
64
- @values = values
65
- 16.times { |i| @values[i] ||= Value.new(rel: :mem, val: i) } # make @values always has all keys
66
- @values[:a] ||= Value.new
67
- @values[:x] ||= Value.new
68
- @known_data = known_data
69
- end
70
-
71
- # Is used for the ld/ldx instructions.
72
- #
73
- # @param [#downcase, :a, :x] reg
74
- # Register to be set
75
- # @return [void]
76
- def load(reg, rel: nil, val: nil)
77
- reg = reg.downcase.to_sym
78
- values[reg] = if rel == :mem
79
- values[val]
80
- else
81
- Value.new(rel:, val:)
82
- end
83
- end
84
-
85
- # Is used for the st/stx instructions.
86
- #
87
- # @param [Integer] idx
88
- # Index of +mem+ array.
89
- # @param [#downcase, :a, :x] reg
90
- # Register.
91
- #
92
- # @return [void]
93
- def store(idx, reg)
94
- raise RangeError, "Expect 0 <= idx < 16, got #{idx}." unless idx.between?(0, 15)
95
-
96
- values[idx] = values[reg.downcase.to_sym]
97
- end
98
-
99
- # Hints context that current value of register A equals to +val+.
100
- #
101
- # @param [Integer, :x] val
102
- # An immediate value or the symbol x.
103
- # @return [self]
104
- # Returns the object itself.
105
- def eql!(val)
106
- tap do
107
- # only cares when A is fetched from data
108
- next unless a.data?
109
- next known_data[a.val] = val if val.is_a?(Integer)
110
- # A == X, we can handle these cases:
111
- # * X is an immi
112
- # * X is a known data
113
- next unless x.data? || x.imm?
114
- next known_data[a.val] = x.val if x.imm?
115
-
116
- known_data[a.val] = known_data[x.val]
117
- end
118
- end
119
-
120
- # Implements a deep dup.
121
- # @return [Context]
122
- def dup
123
- Context.new(values: values.dup, known_data: known_data.dup)
124
- end
125
-
126
- # Register A.
127
- # @return [Context::Value]
128
- def a
129
- values[:a]
130
- end
131
-
132
- # Register X.
133
- # @return [Context::Value]
134
- def x
135
- values[:x]
136
- end
137
-
138
- # For conveniently get instance variable.
139
- # @param [String, Symbol, Integer] key
140
- # @return [Context::Value]
141
- def [](key)
142
- return values[key] if key.is_a?(Integer) # mem
143
-
144
- values[key.downcase.to_sym]
145
- end
146
-
147
- # For conveniently set an instance variable.
148
- # @param [#downcase, :a, :x] reg
149
- # Can be +'A', 'a', :a, 'X', 'x', :x+.
150
- # @param [Value] val
151
- # Value to set.
152
- # @return [void]
153
- def []=(reg, val)
154
- values[reg.downcase.to_sym] = val
155
- end
156
-
157
- # For +Set+ to compare two {Context} objects.
158
- # @param [Context] other
159
- # @return [Boolean]
160
- def eql?(other)
161
- values.eql?(other.values) && known_data.eql?(other.known_data)
162
- end
163
-
164
- # For +Set+ to get the hash value.
165
- # @return [Integer]
166
- def hash
167
- [values, known_data].hash
168
- end
169
- end
170
- end
171
- end