Skip to content

Commit 9db3e9d

Browse files
committed
🥅 Strictly validate symbol (\flag) arguments
Flags should not allow `atom-specials`. Previously, no validation was done on symbol data. Sending atom or flag args which contain atom specials could lead to various errors. Although this could theoretically include injection attacks, this is not considered to be a critical vulnerability in `net-imap`, for the following reason: Valid "system flag" inputs are restricted to an enumerated set of RFC-defined flag types. User-defined "keyword" flags are sent as atoms, not flags, which use string inputs (strings which can't be sent as an atom will be quoted or sent as a literal). `\Seen` as a flag (symbol argument) is semantically different from `Seen` as a keyword (string argument). So there is no scenario where it is appropriate to call `#to_sym` on unvetted user input. Any code which calls `#to_sym` indiscriminately on user-input is already buggy. Nevertheless, users should reasonably be able to rely on `net-imap` to do very basic input validation on its basic input types.
1 parent 99f59ea commit 9db3e9d

3 files changed

Lines changed: 117 additions & 6 deletions

File tree

lib/net/imap/command_data.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def validate_data(data)
2727
end
2828
when Time, Date, DateTime
2929
when Symbol
30+
Flag.validate(data)
3031
else
3132
data.validate
3233
end
@@ -47,7 +48,7 @@ def send_data(data, tag = nil)
4748
when Date
4849
send_date_data(data)
4950
when Symbol
50-
send_symbol_data(data)
51+
Flag[data].send_data(self, tag)
5152
else
5253
data.send_data(self, tag)
5354
end
@@ -138,11 +139,13 @@ def send_list_data(list, tag = nil)
138139
def send_date_data(date) put_string Net::IMAP.encode_date(date) end
139140
def send_time_data(time) put_string Net::IMAP.encode_time(time) end
140141

141-
def send_symbol_data(symbol)
142-
put_string("\\" + symbol.to_s)
143-
end
144-
145142
CommandData = Data.define(:data) do # :nodoc:
143+
def self.validate(...)
144+
data = new(...)
145+
data.validate
146+
data
147+
end
148+
146149
def send_data(imap, tag)
147150
raise NoMethodError, "#{self.class} must implement #{__method__}"
148151
end
@@ -158,8 +161,26 @@ def send_data(imap, tag)
158161
end
159162

160163
class Atom < CommandData # :nodoc:
164+
def initialize(**)
165+
super
166+
validate
167+
end
168+
169+
def validate
170+
data.to_s.ascii_only? \
171+
or raise DataFormatError, "#{self.class} must be ASCII only"
172+
data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
173+
and raise DataFormatError, "#{self.class} must not contain atom-specials"
174+
end
175+
161176
def send_data(imap, tag)
162-
imap.__send__(:put_string, data)
177+
imap.__send__(:put_string, data.to_s)
178+
end
179+
end
180+
181+
class Flag < Atom # :nodoc:
182+
def send_data(imap, tag)
183+
imap.__send__(:put_string, "\\#{data}")
163184
end
164185
end
165186

test/net/imap/test_command_data.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
class CommandDataTest < Net::IMAP::TestCase
77
DataFormatError = Net::IMAP::DataFormatError
88

9+
Atom = Net::IMAP::Atom
10+
Flag = Net::IMAP::Flag
911
Literal = Net::IMAP::Literal
1012
Literal8 = Net::IMAP::Literal8
1113

@@ -60,6 +62,66 @@ def send_data(*data, tag: TAG)
6062
@imap = FakeCommandWriter.new
6163
end
6264

65+
test "Atom" do
66+
imap.send_data(Atom[:INBOX], Atom["INBOX"], Atom["etc"])
67+
assert_equal [
68+
Output.put_string("INBOX"),
69+
Output.put_string("INBOX"),
70+
Output.put_string("etc"),
71+
], imap.output
72+
73+
imap.clear
74+
# atom may not contain atom-specials
75+
[
76+
"with_parens()",
77+
"with_list_wildcards*",
78+
"with_list_wildcards%",
79+
"with_resp_special]",
80+
"with\0null",
81+
"with\x7fcontrol_char",
82+
'"with_quoted_specials"',
83+
"with_quoted_specials\\",
84+
"with\rCR",
85+
"with\nLF",
86+
].each do |symbol|
87+
assert_raise_with_message(Net::IMAP::DataFormatError, /\batom\b/i) do
88+
imap.send_data Atom[symbol]
89+
end
90+
end
91+
assert_empty imap.output
92+
end
93+
94+
test "Flag" do
95+
imap.send_data(Flag[:Seen], Flag[:Flagged],
96+
Flag["Deleted"], Flag["Answered"])
97+
assert_equal [
98+
Output.put_string("\\Seen"),
99+
Output.put_string("\\Flagged"),
100+
Output.put_string("\\Deleted"),
101+
Output.put_string("\\Answered"),
102+
], imap.output
103+
104+
imap.clear
105+
# symbol may not contain atom-specials
106+
[
107+
:"with_parens()",
108+
:"with_list_wildcards*",
109+
:"with_list_wildcards%",
110+
:"with_resp_special]",
111+
:"with\0null",
112+
:"with\x7fcontrol_char",
113+
:'"with_quoted_specials"',
114+
:"with_quoted_specials\\",
115+
:"with\rCR",
116+
:"with\nLF",
117+
].each do |symbol|
118+
assert_raise_with_message(Net::IMAP::DataFormatError, /\bflag\b/i) do
119+
imap.send_data Flag[symbol]
120+
end
121+
end
122+
assert_empty imap.output
123+
end
124+
63125
test "Literal" do
64126
imap.send_data Literal["foo\r\nbar"]
65127
imap.send_data Literal["foo\r\nbar", false]

test/net/imap/test_imap.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,34 @@ def test_send_invalid_number
622622
end
623623
end
624624

625+
def test_send_symbol_as_flag
626+
with_fake_server do |server, imap|
627+
server.on "TEST", &:done_ok
628+
629+
imap.__send__(:send_command, "TEST", :Seen, :Flagged)
630+
assert_equal "\\Seen \\Flagged", server.commands.pop.args
631+
632+
# symbol may not contain atom-specials
633+
[
634+
:"with_parens()",
635+
:"with_list_wildcards*",
636+
:"with_list_wildcards%",
637+
:"with_resp_special]",
638+
:"with\0null",
639+
:"with\x7fcontrol_char",
640+
:'"with_quoted_specials"',
641+
:"with_quoted_specials\\",
642+
:"with\rCR",
643+
:"with\nLF",
644+
].each do |symbol|
645+
assert_raise_with_message(Net::IMAP::DataFormatError, /\bflag\b/i) do
646+
imap.__send__(:send_command, "TEST", symbol)
647+
end
648+
assert_empty server.commands
649+
end
650+
end
651+
end
652+
625653
test("send PartialRange args") do
626654
with_fake_server do |server, imap|
627655
server.on "TEST", &:done_ok

0 commit comments

Comments
 (0)