1
0
mirror of https://github.com/samba-team/samba.git synced 2025-12-12 12:23:50 +03:00
Files
samba-mirror/python/samba/tests/bcrypt_rsakey_blob.py
Douglas Bagnall f1a8280169 librpc:bcrypt_rsakey_blob: exponent and modulus lengths can't be zero
Apart from it making no sense, without these ranges we end up
allocating a NULL buffer and aborting.

We also put a maximum size on the RSA key, in case we could get
tricked into a DoS by pulling a large buffer and trying crypto maths
on it.

 6 0x572ebce2749a in talloc_abort samba/lib/talloc/talloc.c:506:3
 7 0x572ebce271d4 in talloc_chunk_from_ptr samba/lib/talloc/talloc.c:0
 8 0x572ebce271d4 in __talloc_with_prefix samba/lib/talloc/talloc.c:762:12
 9 0x572ebce235f9 in __talloc samba/lib/talloc/talloc.c:825:9
10 0x572ebce235f9 in _talloc_named_const samba/lib/talloc/talloc.c:982:8
11 0x572ebce235f9 in _talloc_memdup samba/lib/talloc/talloc.c:2441:9
12 0x572ebc8f6a4f in data_blob_talloc_named samba/lib/util/data_blob.c:56:25
13 0x572ebc7d23bd in pull_BCRYPT_RSAPUBLIC_BLOB samba/librpc/ndr/ndr_keycredlink.c:878:17
14 0x572ebc7d23bd in ndr_pull_KeyMaterialInternal samba/librpc/ndr/ndr_keycredlink.c:959:10
15 0x572ebc788e90 in LLVMFuzzerTestOneInput samba/bin/default/lib/fuzzing/fuzz_ndr_keycredlink_TYPE_STRUCT.c:282:13

REF: https://issues.oss-fuzz.com/issues/435039896

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>

Autobuild-User(master): Douglas Bagnall <dbagnall@samba.org>
Autobuild-Date(master): Thu Jul 31 05:45:07 UTC 2025 on atb-devel-224
2025-07-31 05:45:07 +00:00

217 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
# Tests for NDR packing and unpacking of BCRYPT_RSAPUBLIC_BLOB structures
#
# See https://learn.microsoft.com/en-us/windows/win32/api/
# bcrypt/ns-bcrypt-bcrypt_rsakey_blob
#
# Copyright (C) Gary Lockyer 2025
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import os
sys.path.insert(0, "bin/python")
os.environ["PYTHONUNBUFFERED"] = "1"
from samba.dcerpc import bcrypt_rsakey_blob
from samba.ndr import ndr_pack, ndr_unpack
from samba.tests import TestCase
class BcryptRsaKeyBlobTests(TestCase):
def test_unpack_empty_key_blob(self):
"""
Ensure that a minimal header only BCRYPT_RSAPUBLIC_BLOB
can't be unpacked, because it would imply zero length modulus
and exponent numbers, which is meaningless.
"""
empty_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length
"00 00 00 00" # modulus length"
"00 00 00 00" # prime one length"
"00 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
empty_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_invalid_magic(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with an invalid magic value is
rejected
"""
invalid_magic_key_blob = bytes.fromhex(
"52 53 41 30" # Magic value RSA0
"04 00 00 00" # bit length
"01 00 00 00" # public exponent length
"01 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length"
"01 02" # exponent and modulus, one byte each
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_magic_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_extra_data(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with extra data is
rejected
"""
extra_data_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"04 00 00 00" # bit length
"01 00 00 00" # public exponent length
"01 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length
"01 02" # exponent and modulus, one byte each
"01" # a trailing byte of data
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
extra_data_key_blob)
self.assertEqual(e.exception.args[0], 18)
self.assertEqual(e.exception.args[1], "Unread Bytes")
def test_unpack_missing_data(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with missing data is
rejected
"""
short_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"08 00 00 00" # bit length, 2048
"01 00 00 00" # public exponent length, one byte
"02 00 00 00" # modulus length, two bytes
"00 00 00 00" # prime one length must be zero
"00 00 00 00" # prime two length must be zero
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB, short_key_blob)
self.assertEqual(e.exception.args[0], 11)
self.assertEqual(e.exception.args[1], "Buffer Size Error")
def test_unpack_invalid_exponent_length(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with an invalid exponent length is
rejected
"""
invalid_magic_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"08 00 00 00" # bit length
"09 00 00 00" # public exponent length, 9 bytes
"01 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_magic_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_non_zero_prime1(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with a non zero prime 1 length is
rejected
"""
invalid_prime1_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"04 00 00 00" # bit length
"01 00 00 00" # public exponent length
"01 00 00 00" # modulus length
"01 00 00 00" # prime one length
"00 00 00 00" # prime two length"
"01 02" # exponent and modulus, one byte each
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_prime1_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_non_zero_prime2(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with a non zero prime 2 length is
rejected
"""
invalid_prime2_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"01 00 00 00" # public exponent length
"01 00 00 00" # modulus length
"00 00 00 00" # prime one length
"01 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_prime2_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack(self):
"""
Ensure that a fully populated BCRYPT_RSAPUBLIC_BLOB
can be unpacked, then packed into identical bytes
"""
key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 08 00 00" # bit length, 2048
"01 00 00 00" # public exponent length
"02 00 00 00" # modulus length"
"00 00 00 00" # prime one length"
"00 00 00 00" # prime two length"
"01" # public exponent
"02 03" # modulus
)
blob = ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB, key_blob)
self.assertEqual(blob.magic, 0x31415352)
self.assertEqual(blob.bit_length, 2048)
self.assertEqual(blob.public_exponent_len, 1)
self.assertEqual(len(blob.public_exponent), 1)
self.assertEqual(bytes(blob.public_exponent), bytes.fromhex("01"))
self.assertEqual(blob.modulus_len, 2)
self.assertEqual(len(blob.modulus), 2)
self.assertEqual(bytes(blob.modulus), bytes.fromhex("02 03"))
self.assertEqual(blob.prime1_len_unused, 0)
self.assertEqual(blob.prime2_len_unused, 0)
packed = ndr_pack(blob)
self.assertEqual(key_blob, packed)
if __name__ == "__main__":
import unittest
unittest.main()