Repository navigation
uuid.getnode() is not tied to MAC address when using libuuid #132710
Description
Bug report
Bug description:
According to the docs, uuid.getnode() is meant to return a number based on the MAC address of the network interface. However, if Python is built with libuuid, this does not match the observed behavior. Instead, getnode() often produces a random number.
Versions of Python obtained through python.org, brew, and pyenv don't appear to display this bug. But versions obtained through uv and python-build-standalone do.
The key difference seems to be which branch of this try block inside uuid.py is executed:
# 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 = NoneWhen the top branch executes, getnode() produces a random number.
When the bottom branch executes, getnode() produces a number tied to the MAC address of the network interface.
Steps to reproduce:
Case 1: working as intended
Using a version of Python compiled with these flags...
HAVE_UUID_CREATE = "0"
HAVE_UUID_ENC_BE = "0"
HAVE_UUID_GENERATE_TIME_SAFE = "1"
HAVE_UUID_H = "1"
HAVE_UUID_UUID_H = "1"
...we get this behavior:
$ python -c "import uuid; print(uuid.getnode())"
some number X
$ python -c "import uuid; print(uuid.getnode())"
the same number XCase 2: buggy behavior
Using a version of Python compiled with these flags...
HAVE_UUID_CREATE = "0"
HAVE_UUID_ENC_BE = "0"
HAVE_UUID_GENERATE_TIME_SAFE = "0"
HAVE_UUID_H = "0"
HAVE_UUID_UUID_H = "1"
...we get this behavior:
$ python -c "import uuid; print(uuid.getnode())"
some number X
$ python -c "import uuid; print(uuid.getnode())"
some other number Y!!!CPython versions tested on:
3.13
Operating systems tested on:
macOS, Linux
Linked PRs
- gh-132710: only use stable
_uuid.generate_time_safe()to deduce MAC address #132901 - [3.14] gh-132710: only use stable
_uuid.generate_time_safe()to deduce MAC address (GH-132901) #134697 - [3.13] gh-132710: only use stable
_uuid.generate_time_safe()to deduce MAC address (GH-132901) #134704 [to be merged in August 2025] - gh-132710: add missing NEWS entry for GH-132901 #134705
- [3.14] gh-132710: add missing NEWS entry for GH-134697 (GH-134705) #134707
- [3.13] gh-132710: add missing NEWS entry for GH-134704 (GH-134705) #134709 [included in [3.13] gh-132710: only use stable
_uuid.generate_time_safe()to deduce MAC address (GH-132901) #134704]
Activity
sfc-gh-tteixeira commentedon Apr 18, 2025
One thing that caught my eye in the
uuidmodule is this line:In binaries where
_uuid._generate_time_safeis available, my understanding is that_unix_getnodewill always return a random number. And where not available it returnsNone.So shouldn't
_unix_getnodebe a used as fallback rather than the first getter?That is, shouldn't that line be:
?
savannahostrowski commentedon Apr 18, 2025
@picnixz would you be the right person to look at this :) I know you've been doing some work in this space
picnixz commentedon Apr 18, 2025
_unix_getnodeis only used to actually get the node. The node is meant to be constant across UUIDs, so what we do is:So it's correct to first get
_unix_getnodeas the first getter. Now, if_unix_getnodereturns a random number, the issue is either because of how we use libuuid or an issue in libuuid itself.I'll have a more in-depth look tomorrow. I'll also try to build locally to see what happens.
zanieb commentedon Apr 19, 2025
As a note,
HAVE_UUID_GENERATE_TIME_SAFEisn't necessarily the reason for the problem. It's presumably the implementation in the release oflibuuidwhichpython-build-standaloneuses that's problematic. Other builds, e.g., Debian, use a differentlibuuidimplementation in whichHAVE_UUID_GENERATE_TIME_SAFEis set but the node is not random. That said,libuuid's documentation does not say the MAC is guaranteed to be available, so it may be questionable to depend on it as the first lookup method without verifying it is indeed static.picnixz commentedon Apr 19, 2025
That's a good thing to check. Let's just generate a bunch of node addresses, and if they change, fallback to something else.
picnixz commentedon Apr 19, 2025
I've looked at the implementation of uuid.h that PBS uses:
The UUID generation is as follows:
What we call is
And the generic implementation is:
Now what's important is the following:
If the node cannot be generated, a random one is generated. This is the reason (probably) why we have different node values. However, I see some reasons why we're re-doing the
init()path.has_initis being modified by different threads.get_node_iddoesn't work well).I'd say the variable should have at least be declared as
static volatileinstead ofstatic(note that this doesn't guarantee atomicity at all).picnixz commentedon Apr 19, 2025
Note: locally on my machine (openSUSE 15.5, ASUS ROG), I don't have different nodes being created.
picnixz commentedon Apr 19, 2025
Note 2: actually, I doubt I can do anything on Python's side. The reason is that the result is cached so changes can only occur if we have different interpreters runs and not just multiple function calls. One way to do it is to materialize two different Python interpreters for the sake of checking but I don't know if it's not just slower then...
EDIT: Oh I may have a small idea... we could create two configure programs and check for the node's output. If it gives two different results, then we can assume that the function is unstable. But this would only work if for non-cross-builds so I don't know if it can work for the python-build-standalone project @zanieb
[-]uuid.getnode() is not tied to MAC address[/-][+]uuid.getnode() is not tied to MAC address when using `libuuid`[/+]zanieb commentedon Apr 20, 2025
Ah, this stinks. Hm..
I think this makes sense, but to start we really just need a variable that's not
HAVE_UUID_GENERATE_TIME_SAFEwhich determines if this is the method that should be used? LikeHAVE_STABLE_UUID_GET_NODEor something. Then, for cross-builds it can be set manually but in other builds you can detect it as described above?Digging into the root cause... I wonder why
get_node_idis not returning a node though? I actually don't see where critical values likeHAVE_NET_IF_H,HAVE_NET_IF_DL_H, andHAVE_SA_LENare ever set? I'm poking at a patch for that to see if it resolves the issue. It seems feasible I can backport whatever makes this work in the latest util-linux version.Rename stable_random_machine_id to machine_id_v4 / installation_id_v4 (…
picnixz commentedon Apr 24, 2025
Good news is: I think I found a way to fix the issue and will push the fix by the end of the week
13 remaining items
picnixz commentedon May 26, 2025
Marking as a deferred blocker so that we remember to merge the 3.13 backport after a few beta releases.
pythongh-132710: only use stable `_uuid.generate_time_safe()` to dedu…
pythongh-132710: add missing NEWS entry for pythonGH-132901 (python#1…
hugovk commentedon Jul 22, 2025
3.14 RC1 note: this has been fixed for 3.14, the
deferred-blockerlabel is for 3.13.Yhg1s commentedon Aug 4, 2025
Should this go into 3.13.6 (scheduled for tomorrow)?
picnixz commentedon Aug 4, 2025
Mmh... I'm unfortunately not available until tomorrow, so unless I do something this night, we can wait for 3.13.7. As there are some PR dependencies here and there, either someone else does it, or we wait for one release (and I'll merge when I'm back at the end of the week). I should have checked the 3.13 timeline, my bad (it's not a critical bugfix I think, no one has ever complained about this for the past 10 years...)
pythongh-132710: only use stable `_uuid.generate_time_safe()` to dedu…
pythongh-132710: add missing NEWS entry for pythonGH-132901 (python#1…
[3.13] gh-132710: only use stable `_uuid.generate_time_safe()` to ded…
pythongh-132710: only use stable `_uuid.generate_time_safe()` to dedu…
pythongh-132710: add missing NEWS entry for pythonGH-132901 (python#1…