diff --git a/hwdb.d/20-dmi-id.hwdb b/hwdb.d/20-dmi-id.hwdb index a5761e5e99e..7b631cc0d58 100644 --- a/hwdb.d/20-dmi-id.hwdb +++ b/hwdb.d/20-dmi-id.hwdb @@ -90,3 +90,4 @@ dmi:*:svnSupermicro:pnSuper Server:* dmi:*:svnSupermicro:pnSuperServer:* ID_PRODUCT_NAME_IS_RUBBISH=1 ID_PRODUCT_VERSION_IS_RUBBISH=1 + ID_PRODUCT_SERIAL_IS_RUBBISH=1 diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 23835a1bd96..eb2e6e9a45c 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -263,6 +263,8 @@ def property_grammar(): ('ID_PRODUCT_NAME_IS_RUBBISH', zero_one), ('ID_PRODUCT_VERSION_IS_RUBBISH', zero_one), ('ID_BOARD_VERSION_IS_RUBBISH', zero_one), + ('ID_PRODUCT_SERIAL_IS_RUBBISH', zero_one), + ('ID_BOARD_SERIAL_IS_RUBBISH', zero_one), ('ID_PRODUCT_SKU_IS_RUBBISH', zero_one), ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', zero_one), ('ID_CHASSIS', name_literal), diff --git a/rules.d/60-dmi-id.rules b/rules.d/60-dmi-id.rules index ecea74ec60d..87757e94b5b 100644 --- a/rules.d/60-dmi-id.rules +++ b/rules.d/60-dmi-id.rules @@ -19,6 +19,14 @@ ENV{ID_PRODUCT_SKU_IS_RUBBISH}!="1", ENV{ID_SKU}="$attr{product_sku}" ENV{ID_PRODUCT_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{product_version}" ENV{ID_HARDWARE_VERSION}=="", ENV{ID_BOARD_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{board_version}" +# Hardware serial number. The system serial (DMI type 1) is preferred; fall back to the baseboard +# serial (DMI type 2) when the system serial is missing or flagged as rubbish. This mirrors how +# ID_MODEL and ID_HARDWARE_VERSION are selected above. The serial value itself is not copied into +# the udev database, because DMI serials are root-readable (0400) in sysfs; instead, only the +# selected attribute name is exported so hostnamed can read the raw value as root. +ENV{ID_PRODUCT_SERIAL_IS_RUBBISH}!="1", ATTR{product_serial}!="", ENV{ID_SYSFS_ATTRIBUTE_SERIAL}="product_serial" +ENV{ID_SYSFS_ATTRIBUTE_SERIAL}=="", ENV{ID_BOARD_SERIAL_IS_RUBBISH}!="1", ATTR{board_serial}!="", ENV{ID_SYSFS_ATTRIBUTE_SERIAL}="board_serial" + # Chassis asset tag ENV{MODALIAS}!="", ATTR{chassis_asset_tag}!="", IMPORT{builtin}="hwdb '$attr{modalias}cat$attr{chassis_asset_tag}:'" ENV{ID_CHASSIS_ASSET_TAG_IS_RUBBISH}!="1", ENV{ID_CHASSIS_ASSET_TAG}="$attr{chassis_asset_tag}" diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a4d857f5381..2bb34631c44 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -460,16 +460,23 @@ static int get_device_tree_sysattr(Context *c, const char *key, char **ret) { } static int get_hardware_serial(Context *c, char **ret) { + _cleanup_free_ char *attr = NULL; int r; assert(c); assert(ret); - FOREACH_STRING(attr, "product_serial", "board_serial") { - r = get_dmi_sysattr(c, attr, ret); - if (r >= 0 || !ERRNO_IS_NEG_DEVICE_ABSENT(r)) - return r; - } + /* 60-dmi-id.rules selects which DMI serial attribute to report: the product serial number + * (DMI type 1) is preferred, unless hwdb flags it as rubbish, in which case the baseboard + * serial number (DMI type 2) is used instead. If both are rubbish (or the system lacks + * DMI), fall through to the device tree serial number. The serial value is read directly + * from sysfs as root, rather than copied into the udev database, to avoid exposing it to + * unprivileged users. */ + r = get_dmi_property(c, "ID_SYSFS_ATTRIBUTE_SERIAL", &attr); + if (r >= 0) + return get_dmi_sysattr(c, attr, ret); + if (!ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return r; return get_device_tree_sysattr(c, "serial-number", ret); } diff --git a/test/units/TEST-71-HOSTNAME.sh b/test/units/TEST-71-HOSTNAME.sh index bcecf3c98d0..2109db4cafc 100755 --- a/test/units/TEST-71-HOSTNAME.sh +++ b/test/units/TEST-71-HOSTNAME.sh @@ -154,13 +154,33 @@ testcase_hardware_serial() { fake_sysfs_dmi echo '1234' >/sys/class/dmi/id/board_serial + # 60-dmi-id.rules sets ID_SYSFS_ATTRIBUTE_SERIAL to the baseboard serial attribute when the + # product serial is missing; sd_device picks up unknown keys from the uevent file as device + # properties, so write ID_SYSFS_ATTRIBUTE_SERIAL the same way the rules file would. + # Use a subsystem with no db (hostnamed-test) to avoid sd_device loading /run/udev/data. + printf 'SUBSYSTEM=hostnamed-test\nID_SYSFS_ATTRIBUTE_SERIAL=board_serial\n' >>/sys/class/dmi/id/uevent stop_hostnamed assert_eq "$(hostnamectl --json=short | jq --raw-output .HardwareSerial)" "1234" # product_serial is preferred over board_serial echo '4321' >/sys/class/dmi/id/product_serial + printf 'SUBSYSTEM=hostnamed-test\nID_SYSFS_ATTRIBUTE_SERIAL=product_serial\n' > /sys/class/dmi/id/uevent stop_hostnamed assert_eq "$(hostnamectl --json=short | jq --raw-output .HardwareSerial)" "4321" + + # hwdb can flag DMI type 1 product_serial as rubbish; 60-dmi-id.rules then sets + # ID_SYSFS_ATTRIBUTE_SERIAL to the DMI type 2 baseboard serial attribute instead, and hostnamed + # reports it even though the (rubbish) product_serial is still readable via sysfs. + printf 'SUBSYSTEM=hostnamed-test\nID_SYSFS_ATTRIBUTE_SERIAL=board_serial\n' > /sys/class/dmi/id/uevent + stop_hostnamed + assert_eq "$(hostnamectl --json=short | jq --raw-output .HardwareSerial)" "1234" + + # When both DMI serials are rubbish, the rules leave ID_SYSFS_ATTRIBUTE_SERIAL unset; hostnamed + # must not read the sysfs attributes directly but fall through to the device tree serial, which + # is absent here. + printf 'SUBSYSTEM=hostnamed-test\n' > /sys/class/dmi/id/uevent + stop_hostnamed + assert_eq "$(hostnamectl --json=short | jq --raw-output .HardwareSerial)" "null" } testcase_nss-myhostname() {