Skip to content

Commit d0fd929

Browse files
committed
Ensure DBus.type constructs a valid Type.
Previously it was easy to construct an invalid signature, even for trivially bad cases. Now, make sure `DBus.type` constructs a valid Single Complete Type, and add `DBus.types` for constructing a valid Signature (0 or more SCTs). Add test cases. The corresponding section of the specification is https://dbus.freedesktop.org/doc/dbus-specification.html#type-system Also, `DBus::Type` was an internal Module and the constructed Class was awkwardly named `DBus::Type::Type`. Merged them (with a compatibility "symlink")
1 parent 06f762f commit d0fd929

File tree

5 files changed

+215
-79
lines changed

5 files changed

+215
-79
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
## Unreleased
44

5+
API:
6+
* Renamed the DBus::Type::Type class to DBus::Type
7+
(which was previously a module).
8+
59
Bug fixes:
10+
* Signature validation: Ensure DBus.type produces a valid Type
611
* Detect more malformed messages: non-NUL padding bytes, variants with
712
multiple or no value.
813

doc/Reference.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ by the D-Bus signature.
171171
If the signature expects a Variant
172172
(which is the case for all Properties!) then an explicit mechanism is needed.
173173

174-
1. A pair [{DBus::Type::Type}, value] specifies to marshall *value* as
174+
1. A pair [{DBus::Type}, value] specifies to marshall *value* as
175175
that specified type.
176176
The pair can be produced by {DBus.variant}(signature, value) which
177177
gives the same result as [{DBus.type}(signature), value].

lib/dbus/marshall.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def append_variant(val)
403403
vartype = nil
404404
if val.is_a?(Array) && val.size == 2
405405
case val[0]
406-
when DBus::Type::Type
406+
when Type
407407
vartype, vardata = val
408408
when String
409409
begin
@@ -427,7 +427,7 @@ def append_variant(val)
427427
@packet += sub.packet
428428
end
429429

430-
# @param child_type [DBus::Type::Type]
430+
# @param child_type [Type]
431431
def append_array(child_type, val)
432432
if val.is_a?(Hash)
433433
raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY

lib/dbus/type.rb

+140-70
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class Prototype < String; end
3333
#
3434
# This module containts the constants of the types specified in the D-Bus
3535
# protocol.
36-
module Type
36+
#
37+
class Type
3738
# Mapping from type number to name and alignment.
3839
TYPE_MAPPING = {
3940
0 => ["INVALID", nil],
@@ -64,91 +65,117 @@ module Type
6465
class SignatureException < Exception
6566
end
6667

67-
# = D-Bus type conversion class
68-
#
69-
# Helper class for representing a D-Bus type.
70-
class Type
71-
# Returns the signature type number.
72-
attr_reader :sigtype
73-
# Return contained member types.
74-
attr_reader :members
75-
76-
# Create a new type instance for type number _sigtype_.
77-
def initialize(sigtype)
78-
if !TYPE_MAPPING.keys.member?(sigtype)
79-
raise SignatureException, "Unknown key in signature: #{sigtype.chr}"
80-
end
68+
# Formerly this was a Module and there was a DBus::Type::Type class
69+
# but the class got too prominent to keep its double double name.
70+
# This is for backward compatibility.
71+
Type = self # rubocop:disable Naming/ConstantName
8172

82-
@sigtype = sigtype
83-
@members = []
84-
end
73+
# Returns the signature type number.
74+
attr_reader :sigtype
75+
# Return contained member types.
76+
attr_reader :members
8577

86-
# Return the required alignment for the type.
87-
def alignment
88-
TYPE_MAPPING[@sigtype].last
78+
# Use {DBus.type} instead, because this allows constructing
79+
# incomplete or invalid types, for backward compatibility.
80+
#
81+
# @param abstract [Boolean] allow abstract types "r" and "e"
82+
# (Enabled for internal usage by {Parser}.)
83+
def initialize(sigtype, abstract: false)
84+
if !TYPE_MAPPING.keys.member?(sigtype)
85+
case sigtype
86+
when ")"
87+
raise SignatureException, "STRUCT unexpectedly closed: )"
88+
when "}"
89+
raise SignatureException, "DICT_ENTRY unexpectedly closed: }"
90+
else
91+
raise SignatureException, "Unknown type code #{sigtype.inspect}"
92+
end
8993
end
9094

91-
# Return a string representation of the type according to the
92-
# D-Bus specification.
93-
def to_s
94-
case @sigtype
95+
unless abstract
96+
case sigtype
9597
when STRUCT
96-
"(#{@members.collect(&:to_s).join})"
97-
when ARRAY
98-
"a#{child}"
98+
raise SignatureException, "Abstract STRUCT, use \"(...)\" instead of \"#{STRUCT}\""
9999
when DICT_ENTRY
100-
"{#{@members.collect(&:to_s).join}}"
101-
else
102-
if !TYPE_MAPPING.keys.member?(@sigtype)
103-
raise NotImplementedError
104-
end
105-
106-
@sigtype.chr
100+
raise SignatureException, "Abstract DICT_ENTRY, use \"{..}\" instead of \"#{DICT_ENTRY}\""
107101
end
108102
end
109103

110-
# Add a new member type _item_.
111-
def <<(item)
112-
if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
113-
raise SignatureException
114-
end
115-
raise SignatureException if @sigtype == ARRAY && !@members.empty?
116-
117-
if @sigtype == DICT_ENTRY
118-
case @members.size
119-
when 2
120-
raise SignatureException, "Dict entries have exactly two members"
121-
when 0
122-
if [STRUCT, ARRAY, DICT_ENTRY].member?(item.sigtype)
123-
raise SignatureException, "Dict entry keys must be basic types"
124-
end
125-
end
104+
@sigtype = sigtype
105+
@members = []
106+
end
107+
108+
# Return the required alignment for the type.
109+
def alignment
110+
TYPE_MAPPING[@sigtype].last
111+
end
112+
113+
# Return a string representation of the type according to the
114+
# D-Bus specification.
115+
def to_s
116+
case @sigtype
117+
when STRUCT
118+
"(#{@members.collect(&:to_s).join})"
119+
when ARRAY
120+
"a#{child}"
121+
when DICT_ENTRY
122+
"{#{@members.collect(&:to_s).join}}"
123+
else
124+
if !TYPE_MAPPING.keys.member?(@sigtype)
125+
raise NotImplementedError
126126
end
127-
@members << item
127+
128+
@sigtype.chr
128129
end
130+
end
129131

130-
# Return the first contained member type.
131-
def child
132-
@members[0]
132+
# Add a new member type _item_.
133+
def <<(item)
134+
if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
135+
raise SignatureException
133136
end
137+
raise SignatureException if @sigtype == ARRAY && !@members.empty?
134138

135-
def inspect
136-
s = TYPE_MAPPING[@sigtype].first
137-
if [STRUCT, ARRAY].member?(@sigtype)
138-
s += ": #{@members.inspect}"
139+
if @sigtype == DICT_ENTRY
140+
case @members.size
141+
when 2
142+
raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}"
143+
when 0
144+
if [STRUCT, ARRAY, DICT_ENTRY, VARIANT].member?(item.sigtype)
145+
raise SignatureException, "DICT_ENTRY key must be basic (non-container)"
146+
end
139147
end
140-
s
141148
end
149+
@members << item
150+
end
151+
152+
# Return the first contained member type.
153+
def child
154+
@members[0]
155+
end
156+
157+
def inspect
158+
s = TYPE_MAPPING[@sigtype].first
159+
if [STRUCT, ARRAY].member?(@sigtype)
160+
s += ": #{@members.inspect}"
161+
end
162+
s
142163
end
143164

144165
# = D-Bus type parser class
145166
#
146167
# Helper class to parse a type signature in the protocol.
168+
# @api private
147169
class Parser
148170
# Create a new parser for the given _signature_.
149171
# @param signature [Signature]
150172
def initialize(signature)
151173
@signature = signature
174+
if signature.size > 255
175+
msg = "Potential signature is longer than 255 characters (#{@signature.size}): #{@signature}"
176+
raise SignatureException, msg
177+
end
178+
152179
@idx = 0
153180
end
154181

@@ -160,35 +187,52 @@ def nextchar
160187
end
161188

162189
# Parse one character _char_ of the signature.
163-
def parse_one(char)
190+
# @param for_array [Boolean] are we parsing an immediate child of an ARRAY
191+
def parse_one(char, for_array: false)
164192
res = nil
165193
case char
166194
when "a"
167195
res = Type.new(ARRAY)
168196
char = nextchar
169-
raise SignatureException, "Parse error in #{@signature}" if char.nil?
197+
raise SignatureException, "Empty ARRAY in #{@signature}" if char.nil?
170198

171-
child = parse_one(char)
199+
child = parse_one(char, for_array: true)
172200
res << child
173201
when "("
174-
res = Type.new(STRUCT)
202+
res = Type.new(STRUCT, abstract: true)
175203
while (char = nextchar) && char != ")"
176204
res << parse_one(char)
177205
end
178-
raise SignatureException, "Parse error in #{@signature}" if char.nil?
206+
raise SignatureException, "STRUCT not closed in #{@signature}" if char.nil?
207+
raise SignatureException, "Empty STRUCT in #{@signature}" if res.members.empty?
179208
when "{"
180-
res = Type.new(DICT_ENTRY)
181-
while (char = nextchar) && char != "}"
209+
raise SignatureException, "DICT_ENTRY not an immediate child of an ARRAY" unless for_array
210+
211+
res = Type.new(DICT_ENTRY, abstract: true)
212+
213+
# key type, value type
214+
2.times do |i|
215+
char = nextchar
216+
raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
217+
218+
raise SignatureException, "DICT_ENTRY must have 2 subtypes, found #{i} in #{@signature}" if char == "}"
219+
182220
res << parse_one(char)
183221
end
184-
raise SignatureException, "Parse error in #{@signature}" if char.nil?
222+
223+
# closing "}"
224+
char = nextchar
225+
raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
226+
227+
raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}" if char != "}"
185228
else
186229
res = Type.new(char)
187230
end
188231
res
189232
end
190233

191234
# Parse the entire signature, return a DBus::Type object.
235+
# @return [Array<Type>]
192236
def parse
193237
@idx = 0
194238
ret = []
@@ -197,17 +241,43 @@ def parse
197241
end
198242
ret
199243
end
244+
245+
# Parse one {SingleCompleteType}
246+
# @return [Type]
247+
def parse1
248+
c = nextchar
249+
raise SignatureException, "Empty signature, expecting a Single Complete Type" if c.nil?
250+
251+
t = parse_one(c)
252+
raise SignatureException, "Has more than a Single Complete Type: #{@signature}" unless nextchar.nil?
253+
254+
t
255+
end
200256
end
201257
end
202258

203259
# shortcuts
204260

205-
# Parse a String to a DBus::Type::Type
261+
# Parse a String to a valid {DBus::Type}.
262+
# This is prefered to {Type#initialize} which allows
263+
# incomplete or invalid types.
264+
# @param string_type [SingleCompleteType]
265+
# @return [DBus::Type]
266+
# @raise SignatureException
206267
def type(string_type)
207-
Type::Parser.new(string_type).parse[0]
268+
Type::Parser.new(string_type).parse1
208269
end
209270
module_function :type
210271

272+
# Parse a String to zero or more {DBus::Type}s.
273+
# @param string_type [Signature]
274+
# @return [Array<DBus::Type>]
275+
# @raise SignatureException
276+
def types(string_type)
277+
Type::Parser.new(string_type).parse
278+
end
279+
module_function :types
280+
211281
# Make an explicit [Type, value] pair
212282
def variant(string_type, value)
213283
[type(string_type), value]

0 commit comments

Comments
 (0)