mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-10-21 11:08:44 +00:00
fix h2 plain padding
This commit is contained in:
1 parent
9109570d32
commit
59c3a6e3b3
3 files changed
+54
-4
No files matched your search
+8
-3
@@ -21,6 +21,7 @@ from .utils import (
|
|||||||
|
|
||||||
log = logging.getLogger('tg-mtproto-proxy')
|
log = logging.getLogger('tg-mtproto-proxy')
|
||||||
MAX_PACKET = 4 * 1024 * 1024
|
MAX_PACKET = 4 * 1024 * 1024
|
||||||
|
MAX_PLAIN_PADDING = 256
|
||||||
MAX_CHANNEL_REQUESTS = 8
|
MAX_CHANNEL_REQUESTS = 8
|
||||||
MAX_CHANNEL_BYTES = 8 * 1024 * 1024
|
MAX_CHANNEL_BYTES = 8 * 1024 * 1024
|
||||||
MAX_LANE_REQUESTS = 64
|
MAX_LANE_REQUESTS = 64
|
||||||
@@ -116,16 +117,20 @@ async def _read_packet(reader, decryptor, tag: bytes) -> Tuple[bytes, bool]:
|
|||||||
else:
|
else:
|
||||||
value, = struct.unpack('<I', await plaintext(4))
|
value, = struct.unpack('<I', await plaintext(4))
|
||||||
quick, length = bool(value & 0x80000000), value & 0x7fffffff
|
quick, length = bool(value & 0x80000000), value & 0x7fffffff
|
||||||
if not 24 <= length <= MAX_PACKET + (15 if tag == PROTO_TAG_SECURE else 0):
|
if not 24 <= length <= MAX_PACKET + (MAX_PLAIN_PADDING if tag == PROTO_TAG_SECURE else 0):
|
||||||
raise ValueError('native packet length outside supported range: %d' % length)
|
raise ValueError('native packet length outside supported range: %d' % length)
|
||||||
body = await plaintext(length)
|
body = await plaintext(length)
|
||||||
if tag == PROTO_TAG_SECURE:
|
if tag == PROTO_TAG_SECURE:
|
||||||
if body[:8] == b'\x00' * 8:
|
if body[:8] == b'\x00' * 8:
|
||||||
packet_length = 20 + int.from_bytes(body[16:20], 'little')
|
packet_length = 20 + int.from_bytes(body[16:20], 'little')
|
||||||
|
max_padding = MAX_PLAIN_PADDING
|
||||||
else:
|
else:
|
||||||
packet_length = 24 + ((length - 24) // 16) * 16
|
packet_length = 24 + ((length - 24) // 16) * 16
|
||||||
if not 24 <= packet_length <= length or length - packet_length > 15:
|
max_padding = 15
|
||||||
raise ValueError('invalid padded native packet')
|
padding = length - packet_length
|
||||||
|
if not 24 <= packet_length <= length or padding > max_padding:
|
||||||
|
raise ValueError('invalid padded native packet: frame=%d packet=%d padding=%d limit=%d' % (
|
||||||
|
length, packet_length, padding, max_padding))
|
||||||
body = body[:packet_length]
|
body = body[:packet_length]
|
||||||
if len(body) % 4 or len(body) > MAX_PACKET:
|
if len(body) % 4 or len(body) > MAX_PACKET:
|
||||||
raise ValueError('invalid native packet alignment or size')
|
raise ValueError('invalid native packet alignment or size')
|
||||||
|
|||||||
@@ -56,6 +56,47 @@ class NativeFramingTest(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertEqual(decoded, body, padding)
|
self.assertEqual(decoded, body, padding)
|
||||||
self.assertFalse(quick)
|
self.assertFalse(quick)
|
||||||
|
|
||||||
|
async def test_plaintext_accepts_extended_padding_used_by_apple_and_android(self):
|
||||||
|
body = b'\x00' * 8 + b'm' * 8 + struct.pack('<I', 20) + b'p' * 20
|
||||||
|
# MTProtoKit appends 0..236 bytes outside message_data_length, then
|
||||||
|
# the TCP transport adds 0..15 more. Android uses 0..256 directly.
|
||||||
|
for padding in range(257):
|
||||||
|
with self.subTest(padding=padding):
|
||||||
|
wire = body + b'z' * padding
|
||||||
|
reader = asyncio.StreamReader()
|
||||||
|
reader.feed_data(struct.pack('<I', len(wire) | 0x80000000) + wire)
|
||||||
|
decoded, quick = await _read_packet(reader, _IdentityCipher(), PROTO_TAG_SECURE)
|
||||||
|
self.assertEqual(decoded, body)
|
||||||
|
self.assertTrue(quick)
|
||||||
|
|
||||||
|
async def test_padded_plaintext_preserves_padding_included_in_message_length(self):
|
||||||
|
body = b'\x00' * 8 + b'm' * 8 + struct.pack('<I', 272) + b'p' * 20 + b'd' * 252
|
||||||
|
decoded, _ = await self._packet(struct.pack('<I', len(body) + 15) + body + b'z' * 15,
|
||||||
|
PROTO_TAG_SECURE)
|
||||||
|
self.assertEqual(decoded, body)
|
||||||
|
|
||||||
|
async def test_extended_padding_preserves_packet_size_limit(self):
|
||||||
|
body = b'\x00' * 8 + b'm' * 8 + struct.pack('<I', 20) + b'p' * 20
|
||||||
|
wire = body + b'z' * 256
|
||||||
|
with patch('proxy.cf_h2.MAX_PACKET', len(body)):
|
||||||
|
decoded, _ = await self._packet(struct.pack('<I', len(wire)) + wire, PROTO_TAG_SECURE)
|
||||||
|
self.assertEqual(decoded, body)
|
||||||
|
oversized = body[:16] + struct.pack('<I', 24) + b'p' * 24
|
||||||
|
with self.assertRaisesRegex(ValueError, 'size'):
|
||||||
|
await self._packet(struct.pack('<I', len(oversized)) + oversized, PROTO_TAG_SECURE)
|
||||||
|
encrypted = b'k' * 8 + b'm' * 16 + b'c' * 32
|
||||||
|
with self.assertRaisesRegex(ValueError, 'size'):
|
||||||
|
await self._packet(struct.pack('<I', len(encrypted)) + encrypted, PROTO_TAG_SECURE)
|
||||||
|
|
||||||
|
async def test_invalid_plaintext_lengths_and_excess_padding_are_rejected(self):
|
||||||
|
for declared, payload, padding in ((0, 20, 0), (24, 20, 0), (21, 21, 0),
|
||||||
|
(20, 20, 257), (0xffffffff, 20, 0)):
|
||||||
|
with self.subTest(declared=declared, padding=padding):
|
||||||
|
body = b'\x00' * 8 + b'm' * 8 + struct.pack('<I', declared) + b'p' * payload
|
||||||
|
wire = body + b'z' * padding
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
await self._packet(struct.pack('<I', len(wire)) + wire, PROTO_TAG_SECURE)
|
||||||
|
|
||||||
async def test_padded_ciphertext_removes_full_padding_blocks(self):
|
async def test_padded_ciphertext_removes_full_padding_blocks(self):
|
||||||
body = b'k' * 8 + b'm' * 16 + b'c' * 32
|
body = b'k' * 8 + b'm' * 16 + b'c' * 32
|
||||||
for padding in range(16):
|
for padding in range(16):
|
||||||
|
|||||||
@@ -607,8 +607,12 @@ class H2WireTest(unittest.IsolatedAsyncioTestCase):
|
|||||||
peers.append(peer)
|
peers.append(peer)
|
||||||
bodies = [b'\x00' * 8 + os.urandom(8) + struct.pack('<I', 20) + os.urandom(20)]
|
bodies = [b'\x00' * 8 + os.urandom(8) + struct.pack('<I', 20) + os.urandom(20)]
|
||||||
bodies += [b'samekey!' + os.urandom(size - 8) for size in (40, 131112, 56)]
|
bodies += [b'samekey!' + os.urandom(size - 8) for size in (40, 131112, 56)]
|
||||||
|
# Maximum extended plaintext padding: MTProtoKit
|
||||||
|
# (236 + 15) and Android (256), not just TCP's 15.
|
||||||
|
plain_padding = (251, 256)[index]
|
||||||
wire = init + up.update(b''.join(
|
wire = init + up.update(b''.join(
|
||||||
frame(body, tag, padding) for body, padding in zip(bodies, (15, 1, 7, 0))))
|
frame(body, tag, padding)
|
||||||
|
for body, padding in zip(bodies, (plain_padding, 1, 7, 0))))
|
||||||
for start, end in ((0, 13), (13, 57), (57, 65), (65, 78), (78, len(wire))):
|
for start, end in ((0, 13), (13, 57), (57, 65), (65, 78), (78, len(wire))):
|
||||||
writer.write(wire[start:end])
|
writer.write(wire[start:end])
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|||||||
Reference in new issue
Block a user