Skip to content

Commit cff7f8e

Browse files
committed
Clean up input validation
- Compile all regular expressions during initialization. - Check for newline injection before sending data to the agent. - Misc cleanups. - Use "parse, don't validate" for commands taking keygrips
1 parent 5890078 commit cff7f8e

1 file changed

Lines changed: 20 additions & 18 deletions

File tree

splitgpg2/__init__.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ def extract_args(untrusted_line: bytes, sep: bytes = b' ') -> Tuple[bytes, Optio
171171
return untrusted_line, None
172172

173173
# none of our uses allow 0, so do not allow it
174-
_int_re = re.compile(rb'\A[1-9][0-9]*\Z')
174+
_int_re: re.Pattern[bytes] = re.compile(rb'\A[1-9][0-9]*\Z')
175+
_hash_regex = re.compile(rb'\A[0-9A-F]+\Z')
175176

176177
def sanitize_int(untrusted_arg: bytes, min_value: int, max_value: int) -> int:
177178
"""
@@ -224,6 +225,8 @@ class GpgServer:
224225
log: logging.Logger
225226

226227
cache_nonce_regex: re.Pattern[bytes] = re.compile(rb'\A[0-9A-F]{24}\Z')
228+
# Any command argument ever sent to the agent should match this pattern.
229+
command_argument_regex: re.Pattern[bytes] = re.compile(rb'\A[0-9A-Za-z_=. -]*\Z')
227230

228231
__slots__ = ('verbose_notifications',
229232
'timer_delay',
@@ -640,15 +643,15 @@ def fake_respond(self, response: bytes) -> None:
640643

641644
@staticmethod
642645
def verify_keygrip_arguments(min_count: int, max_count: int,
643-
untrusted_args: Optional[bytes]) -> bytes:
646+
untrusted_args: Optional[bytes]) -> bytes:
644647
if untrusted_args is None:
645648
raise Filtered
646-
args_regex = re.compile(rb'\A[0-9A-F]{40}( [0-9A-F]{40}){%d,%d}\Z' %
647-
(min_count-1, max_count-1))
648-
649-
if args_regex.match(untrusted_args) is None:
649+
if not (min_count <= len(untrusted_args_list) <= max_count):
650650
raise Filtered
651-
return untrusted_args
651+
for untrusted_arg in untrusted_args_list:
652+
if len(untrusted_arg) != 40 or not _hash_regex.match(untrusted_arg):
653+
raise Filtered
654+
return b' '.join(untrusted_args_list)
652655

653656
def sanitize_key_desc(self, untrusted_args: bytes) -> bytes:
654657
untrusted_args = untrusted_args.replace(b'+', b' ')
@@ -937,30 +940,27 @@ async def command_SETHASH(self, untrusted_args: Optional[bytes]) -> None:
937940
except KeyError as e:
938941
raise Filtered from e
939942

940-
if not untrusted_hash:
943+
if len(untrusted_hash) != alg_param.len:
941944
raise Filtered
942945

943-
hash_regex = re.compile(rb'\A[0-9A-F]{%d}\Z' % alg_param.len)
944-
if hash_regex.match(untrusted_hash) is None:
946+
if not _hash_regex.match(untrusted_hash):
945947
raise Filtered
946948
hash_value = untrusted_hash
947949

948-
await self.send_agent_command(
949-
b'SETHASH', b'%d %s' % (alg, hash_value))
950+
# Hash values and ASCII decimal numbers are safe to pass.
951+
await self.send_agent_command(b'SETHASH', b'%d %s' % (alg, hash_value))
950952

951953
async def command_PKSIGN(self, untrusted_args: Optional[bytes]) -> None:
952954
if untrusted_args is not None:
953955
if not untrusted_args.startswith(b'-- '):
954956
raise Filtered
955-
untrusted_args = untrusted_args[3:]
956-
if self.cache_nonce_regex.match(untrusted_args) is None:
957+
if self.cache_nonce_regex.match(untrusted_args[3:]) is None:
957958
raise Filtered
958-
args = b'-- ' + untrusted_args
959-
else:
960-
args = None
959+
args = untrusted_args
961960

962961
self.request_timer('PKSIGN')
963962

963+
# String checked to be '-- ' followed by a cache nonce
964964
await self.send_agent_command(b'PKSIGN', args)
965965

966966
async def command_GETINFO(self, untrusted_args: Optional[bytes]) -> None:
@@ -1019,6 +1019,8 @@ async def send_agent_command(self, command: bytes, args: Optional[bytes]) -> Non
10191019
""" Sends command to local gpg agent and handle the response """
10201020
expected_inquires = self.get_inquires_for_command(command)
10211021
if args:
1022+
if not self.command_argument_regex.match(args):
1023+
raise AssertionError("BUG: corrupt command about to be sent to agent!")
10221024
cmd_with_args = command + b' ' + args + b'\n'
10231025
else:
10241026
cmd_with_args = command + b'\n'
@@ -1355,7 +1357,7 @@ def _parse_sexpr(cls, untrusted_arg: bytes, nesting: int) -> Tuple[List['SExpr']
13551357

13561358
rest: bytes
13571359
value: Union[List['SExpr'], bytes]
1358-
if untrusted_arg[0] in range(0x30, 0x40):
1360+
if 0x30 <= untrusted_arg[0] <= 0x40:
13591361
length_s, rest = untrusted_arg.split(b':', 1)
13601362
length = sanitize_int(length_s, 1, len(rest))
13611363
value, rest = rest[0:length], rest[length:]

0 commit comments

Comments
 (0)