Skip to content

bpo-40501: Replace ctypes code in uuid with native module #19948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions Lib/test/test_uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,17 +852,6 @@ def test_netstat_getnode(self):
node = self.uuid._netstat_getnode()
self.check_node(node, 'netstat')

@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_ipconfig_getnode(self):
node = self.uuid._ipconfig_getnode()
self.check_node(node, 'ipconfig')

@unittest.skipUnless(importable('win32wnet'), 'requires win32wnet')
@unittest.skipUnless(importable('netbios'), 'requires netbios')
def test_netbios_getnode(self):
node = self.uuid._netbios_getnode()
self.check_node(node)

def test_random_getnode(self):
node = self.uuid._random_getnode()
# The multicast bit, i.e. the least significant bit of first octet,
Expand All @@ -874,6 +863,13 @@ def test_random_getnode(self):
node2 = self.uuid._random_getnode()
self.assertNotEqual(node2, node, '%012x' % node)

class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase):
uuid = py_uuid

@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase):
uuid = c_uuid

@unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_unix_getnode(self):
if not importable('_uuid') and not importable('ctypes'):
Expand All @@ -885,19 +881,10 @@ def test_unix_getnode(self):
self.check_node(node, 'unix')

@unittest.skipUnless(os.name == 'nt', 'requires Windows')
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_windll_getnode(self):
node = self.uuid._windll_getnode()
self.check_node(node)


class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase):
uuid = py_uuid

@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase):
uuid = c_uuid


if __name__ == '__main__':
unittest.main()
180 changes: 23 additions & 157 deletions Lib/uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,178 +555,44 @@ def _netstat_getnode():
return _find_mac_under_heading('netstat', '-ian', b'Address')

def _ipconfig_getnode():
"""Get the hardware address on Windows by running ipconfig.exe."""
import os, re, subprocess
first_local_mac = None
dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
try:
import ctypes
buffer = ctypes.create_string_buffer(300)
ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300)
dirs.insert(0, buffer.value.decode('mbcs'))
except:
pass
for dir in dirs:
try:
proc = subprocess.Popen([os.path.join(dir, 'ipconfig'), '/all'],
stdout=subprocess.PIPE,
encoding="oem")
except OSError:
continue
with proc:
for line in proc.stdout:
value = line.split(':')[-1].strip().lower()
if re.fullmatch('(?:[0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
mac = int(value.replace('-', ''), 16)
if _is_universal(mac):
return mac
first_local_mac = first_local_mac or mac
return first_local_mac or None
"""[DEPRECATED] Get the hardware address on Windows."""
# bpo-40501: UuidCreateSequential() is now the only supported approach
return _windll_getnode()

def _netbios_getnode():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply removing _ipconfig_getnode and _netbios_getnode? They are private functions, there is no backward compatibility warranty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last time I was involved in one of those people still broke. I'd rather leave them there as long as they aren't hurting anyone.

"""Get the hardware address on Windows using NetBIOS calls.
See http://support.microsoft.com/kb/118623 for details."""
import win32wnet, netbios
first_local_mac = None
ncb = netbios.NCB()
ncb.Command = netbios.NCBENUM
ncb.Buffer = adapters = netbios.LANA_ENUM()
adapters._pack()
if win32wnet.Netbios(ncb) != 0:
return None
adapters._unpack()
for i in range(adapters.length):
ncb.Reset()
ncb.Command = netbios.NCBRESET
ncb.Lana_num = ord(adapters.lana[i])
if win32wnet.Netbios(ncb) != 0:
continue
ncb.Reset()
ncb.Command = netbios.NCBASTAT
ncb.Lana_num = ord(adapters.lana[i])
ncb.Callname = '*'.ljust(16)
ncb.Buffer = status = netbios.ADAPTER_STATUS()
if win32wnet.Netbios(ncb) != 0:
continue
status._unpack()
bytes = status.adapter_address[:6]
if len(bytes) != 6:
continue
mac = int.from_bytes(bytes, 'big')
if _is_universal(mac):
return mac
first_local_mac = first_local_mac or mac
return first_local_mac or None
"""[DEPRECATED] Get the hardware address on Windows."""
# bpo-40501: UuidCreateSequential() is now the only supported approach
return _windll_getnode()


_generate_time_safe = _UuidCreate = None
_has_uuid_generate_time_safe = None

# Import optional C extension at toplevel, to help disabling it when testing
try:
import _uuid
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
_UuidCreate = getattr(_uuid, "UuidCreate", None)
_has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
except ImportError:
_uuid = None
_generate_time_safe = None
_UuidCreate = None
_has_uuid_generate_time_safe = None


def _load_system_functions():
"""
Try to load platform-specific functions for generating uuids.
"""
global _generate_time_safe, _UuidCreate, _has_uuid_generate_time_safe

if _has_uuid_generate_time_safe is not None:
return

_has_uuid_generate_time_safe = False

if sys.platform == "darwin" and int(os.uname().release.split('.')[0]) < 9:
# The uuid_generate_* functions are broken on MacOS X 10.5, as noted
# in issue #8621 the function generates the same sequence of values
# in the parent process and all children created using fork (unless
# those children use exec as well).
#
# Assume that the uuid_generate functions are broken from 10.5 onward,
# the test can be adjusted when a later version is fixed.
pass
elif _uuid is not None:
_generate_time_safe = _uuid.generate_time_safe
_has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
return

try:
# If we couldn't find an extension module, try ctypes to find
# system routines for UUID generation.
# Thanks to Thomas Heller for ctypes and for his help with its use here.
import ctypes
import ctypes.util

# The uuid_generate_* routines are provided by libuuid on at least
# Linux and FreeBSD, and provided by libc on Mac OS X.
_libnames = ['uuid']
if not sys.platform.startswith('win'):
_libnames.append('c')
for libname in _libnames:
try:
lib = ctypes.CDLL(ctypes.util.find_library(libname))
except Exception: # pragma: nocover
continue
# Try to find the safe variety first.
if hasattr(lib, 'uuid_generate_time_safe'):
_uuid_generate_time_safe = lib.uuid_generate_time_safe
# int uuid_generate_time_safe(uuid_t out);
def _generate_time_safe():
_buffer = ctypes.create_string_buffer(16)
res = _uuid_generate_time_safe(_buffer)
return bytes(_buffer.raw), res
_has_uuid_generate_time_safe = True
break

elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover
_uuid_generate_time = lib.uuid_generate_time
# void uuid_generate_time(uuid_t out);
_uuid_generate_time.restype = None
def _generate_time_safe():
_buffer = ctypes.create_string_buffer(16)
_uuid_generate_time(_buffer)
return bytes(_buffer.raw), None
break

# On Windows prior to 2000, UuidCreate gives a UUID containing the
# hardware address. On Windows 2000 and later, UuidCreate makes a
# random UUID and UuidCreateSequential gives a UUID containing the
# hardware address. These routines are provided by the RPC runtime.
# NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last
# 6 bytes returned by UuidCreateSequential are fixed, they don't appear
# to bear any relationship to the MAC address of any network device
# on the box.
try:
lib = ctypes.windll.rpcrt4
except:
lib = None
_UuidCreate = getattr(lib, 'UuidCreateSequential',
getattr(lib, 'UuidCreate', None))

except Exception as exc:
import warnings
warnings.warn(f"Could not find fallback ctypes uuid functions: {exc}",
ImportWarning)
"""[DEPRECATED] Platform-specific functions loaded at import time"""


def _unix_getnode():
"""Get the hardware address on Unix using the _uuid extension module
or ctypes."""
_load_system_functions()
uuid_time, _ = _generate_time_safe()
return UUID(bytes=uuid_time).node
"""Get the hardware address on Unix using the _uuid extension module."""
if _generate_time_safe:
uuid_time, _ = _generate_time_safe()
return UUID(bytes=uuid_time).node

def _windll_getnode():
"""Get the hardware address on Windows using ctypes."""
import ctypes
_load_system_functions()
_buffer = ctypes.create_string_buffer(16)
if _UuidCreate(_buffer) == 0:
return UUID(bytes=bytes_(_buffer.raw)).node
"""Get the hardware address on Windows using the _uuid extension module."""
if _UuidCreate:
uuid_bytes = _UuidCreate()
return UUID(bytes_le=uuid_bytes).node

def _random_getnode():
"""Get a random node ID."""
Expand Down Expand Up @@ -755,7 +621,8 @@ def _random_getnode():
elif _DARWIN:
_OS_GETTERS = [_ifconfig_getnode, _arp_getnode, _netstat_getnode]
elif _WINDOWS:
_OS_GETTERS = [_netbios_getnode, _ipconfig_getnode]
# bpo-40201: _windll_getnode will always succeed, so these are not needed
_OS_GETTERS = []
elif _AIX:
_OS_GETTERS = [_netstat_getnode]
else:
Expand Down Expand Up @@ -802,7 +669,6 @@ def uuid1(node=None, clock_seq=None):

# When the system provides a version-1 UUID generator, use it (but don't
# use UuidCreate here because its UUIDs don't conform to RFC 4122).
_load_system_functions()
if _generate_time_safe is not None and node is clock_seq is None:
uuid_time, safely_generated = _generate_time_safe()
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`uuid` no longer uses :mod:`ctypes` to load :file:`libuuid` or
:file:`rpcrt4.dll` at runtime.
54 changes: 49 additions & 5 deletions Modules/_uuidmodule.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Python UUID module that wraps libuuid -
* Python UUID module that wraps libuuid or Windows rpcrt4.dll.
* DCE compatible Universally Unique Identifier library.
*/

Expand All @@ -12,6 +12,12 @@
#include <uuid.h>
#endif

#ifdef MS_WINDOWS
#include <rpc.h>
#endif

#ifndef MS_WINDOWS

static PyObject *
py_uuid_generate_time_safe(PyObject *Py_UNUSED(context),
PyObject *Py_UNUSED(ignored))
Expand All @@ -31,17 +37,50 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context),
return Py_BuildValue("y#i", buf, sizeof(uuid), (int) status);
# else
return Py_BuildValue("y#i", (const char *) &uuid, sizeof(uuid), (int) status);
# endif
#else
# endif /* HAVE_UUID_CREATE */
#else /* HAVE_UUID_GENERATE_TIME_SAFE */
uuid_generate_time(uuid);
return Py_BuildValue("y#O", (const char *) uuid, sizeof(uuid), Py_None);
#endif
#endif /* HAVE_UUID_GENERATE_TIME_SAFE */
}

#else /* MS_WINDOWS */

static PyObject *
py_UuidCreate(PyObject *Py_UNUSED(context),
PyObject *Py_UNUSED(ignored))
{
UUID uuid;
RPC_STATUS res;

Py_BEGIN_ALLOW_THREADS
res = UuidCreateSequential(&uuid);
Py_END_ALLOW_THREADS

switch (res) {
case RPC_S_OK:
case RPC_S_UUID_LOCAL_ONLY:
case RPC_S_UUID_NO_ADDRESS:
/*
All success codes, but the latter two indicate that the UUID is random
rather than based on the MAC address. If the OS can't figure this out,
neither can we, so we'll take it anyway.
*/
return Py_BuildValue("y#", (const char *)&uuid, sizeof(uuid));
}
PyErr_SetFromWindowsErr(res);
return NULL;
}

#endif /* MS_WINDOWS */


static int
uuid_exec(PyObject *module) {
assert(sizeof(uuid_t) == 16);
#ifdef HAVE_UUID_GENERATE_TIME_SAFE
#if defined(MS_WINDOWS)
int has_uuid_generate_time_safe = 0;
#elif defined(HAVE_UUID_GENERATE_TIME_SAFE)
int has_uuid_generate_time_safe = 1;
#else
int has_uuid_generate_time_safe = 0;
Expand All @@ -54,7 +93,12 @@ uuid_exec(PyObject *module) {
}

static PyMethodDef uuid_methods[] = {
#if defined(HAVE_UUID_UUID_H) || defined(HAVE_UUID_H)
{"generate_time_safe", py_uuid_generate_time_safe, METH_NOARGS, NULL},
#endif
#if defined(MS_WINDOWS)
{"UuidCreate", py_UuidCreate, METH_NOARGS, NULL},
#endif
{NULL, NULL, 0, NULL} /* sentinel */
};

Expand Down
Loading