mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-05 23:23:02 +02:00
The base mac address is used by bridges in Infix. This patch adds support for extracting a base mac from the first interface "- 1", e.g., if the mac address of e1 is 08:00:20:00:00:01, the base mac will be 08:00:20:00:00:00. This works with the Infamy test framework (Qeneth) and also with the qemu script, included in this patch. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
343 lines
9.6 KiB
Python
Executable File
343 lines
9.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import importlib.machinery
|
|
import json
|
|
import os
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
|
|
onieprom = importlib.machinery.SourceFileLoader("onieprom","/bin/onieprom").load_module()
|
|
SYSTEM_JSON = "/run/system.json"
|
|
KKIT_IANA_PEM = 61046
|
|
|
|
class DTSystem:
|
|
BASE = "/sys/firmware/devicetree/base"
|
|
INFIX = BASE + "/chosen/infix"
|
|
|
|
def __init__(self):
|
|
self.vpdseq = 0
|
|
|
|
dt = {}
|
|
for root, _, files in os.walk(DTSystem.BASE):
|
|
if "phandle" not in files:
|
|
continue
|
|
|
|
phandle = os.path.join(root, "phandle")
|
|
if not os.path.exists(phandle):
|
|
continue
|
|
|
|
ph, = struct.unpack(">L", open(phandle, "rb").read())
|
|
dt[ph] = root
|
|
|
|
sys = {}
|
|
for root, dirs, _ in os.walk("/sys/devices"):
|
|
if "of_node" not in dirs:
|
|
continue
|
|
|
|
phandle = os.path.join(root, "of_node", "phandle")
|
|
if not os.path.exists(phandle):
|
|
continue
|
|
|
|
ph, = struct.unpack(">L", open(phandle, "rb").read())
|
|
if ph not in sys:
|
|
sys[ph] = []
|
|
sys[ph].append(root)
|
|
|
|
phs = set(list(dt.keys()) + list(sys.keys()))
|
|
|
|
self.devs = { ph: [Device(ph, dt.get(ph), s if s is not None else "") for s in (sys.get(ph) or []) if ph is not None] for ph in phs }
|
|
self.base = Device(0, None, DTSystem.BASE)
|
|
self.infix = Device(0, None, DTSystem.INFIX)
|
|
|
|
def __get_phandle_array(self, name):
|
|
path = os.path.join(DTSystem.INFIX, name)
|
|
if not os.path.exists(path):
|
|
return ()
|
|
|
|
data = open(path, "rb").read()
|
|
elems = len(data) // struct.calcsize(">L")
|
|
return struct.unpack(">" + elems * "L", data)
|
|
|
|
def devices_from_ph(self, ph):
|
|
return self.devs.get(ph)
|
|
|
|
def into_vpd(self, dev):
|
|
def parse():
|
|
if not dev.available():
|
|
return {}
|
|
|
|
try:
|
|
data = onieprom.from_tlv(open(dev.attrpath("nvmem"), "rb", 0))
|
|
except:
|
|
data = {}
|
|
|
|
return data
|
|
|
|
self.vpdseq += 1
|
|
return {
|
|
"board": dev.dtstr("infix,board", f"UNKNOWN{self.vpdseq}"),
|
|
"available": dev.available(),
|
|
"trusted": dev.hasdtattr("infix,trusted"),
|
|
"data": parse(),
|
|
}
|
|
|
|
def infix_usb_devices(self, out):
|
|
names=self.infix.str_array("usb-port-names", ())
|
|
phs=self.__get_phandle_array("usb-ports")
|
|
data=dict(zip(names,phs))
|
|
if data != {}:
|
|
out["usb-ports"] = []
|
|
for name,ph in data.items():
|
|
[out["usb-ports"].extend([{"name": name, "path": dev.attrpath("authorized")}, {"name": name, "path": dev.attrpath("authorized_default")}]) for dev in self.devices_from_ph(ph)]
|
|
|
|
|
|
def infix_devices(self, kind):
|
|
phs = self.__get_phandle_array(kind)
|
|
return [[d for d in self.devices_from_ph(ph)] for ph in phs]
|
|
|
|
def infix_vpds(self):
|
|
flat_devices = [device for sublist in self.infix_devices("vpds") for device in sublist]
|
|
return [self.into_vpd(device) for device in flat_devices]
|
|
|
|
class QEMUSystem:
|
|
BASE = "/sys/firmware/qemu_fw_cfg"
|
|
REV = BASE + "/rev"
|
|
VPD = BASE + "/by_name/opt/vpd/raw"
|
|
|
|
def product_vpd(self):
|
|
data = {}
|
|
if os.path.exists(QEMUSystem.VPD):
|
|
try:
|
|
data = onieprom.from_tlv(open(QEMUSystem.VPD, "rb", 0))
|
|
except:
|
|
pass
|
|
|
|
return {
|
|
"board": "product",
|
|
"available": os.path.exists(QEMUSystem.VPD),
|
|
"trusted": True,
|
|
"data": data,
|
|
}
|
|
|
|
def vpds(self):
|
|
return [self.product_vpd()]
|
|
|
|
def usb_ports(self):
|
|
ports = [
|
|
{
|
|
"name": "USB",
|
|
"path": "/sys/bus/usb/devices/usb1/authorized"
|
|
|
|
},
|
|
{
|
|
"name": "USB",
|
|
"path": "/sys/bus/usb/devices/usb1/authorized_default"
|
|
|
|
},
|
|
{
|
|
"name": "USB2",
|
|
"path": "/sys/bus/usb/devices/usb2/authorized"
|
|
},
|
|
{
|
|
"name": "USB2",
|
|
"path": "/sys/bus/usb/devices/usb2/authorized_default"
|
|
|
|
}]
|
|
return ports
|
|
class Device:
|
|
def __init__(self, ph, dtpath, syspath):
|
|
self.ph, self.dtpath, self.syspath = ph, dtpath, syspath
|
|
|
|
def available(self):
|
|
return self.syspath != None
|
|
|
|
def __getitem__(self, attr):
|
|
return self.attr(attr).decode("utf-8").strip("\0")
|
|
|
|
def __setitem__(self, attr, value):
|
|
return self.attr(attr, val=value.encode("utf-8"))
|
|
|
|
|
|
def attrpath(self, attr):
|
|
return os.path.join(self.syspath, attr)
|
|
|
|
def hasattr(self, attr):
|
|
return os.path.exists(self.attrpath(attr))
|
|
|
|
def attr(self, attr, default=None, val=None):
|
|
if not self.hasattr(attr):
|
|
return default if val == None else False
|
|
|
|
if val:
|
|
open(self.attrpath(attr), "wb").write(value)
|
|
return True
|
|
|
|
return open(self.attrpath(attr), "rb").read()
|
|
|
|
def str(self, attr, default=None):
|
|
val = self.attr(attr)
|
|
return val.decode("utf-8").strip("\0") if val else default
|
|
|
|
def str_array(self, attr, default=None):
|
|
val = self.attr(attr)
|
|
return val.decode("utf-8").strip("\0").split("\0") if val else default
|
|
|
|
def dtattrpath(self, attr):
|
|
return os.path.join(self.dtpath, attr)
|
|
|
|
def hasdtattr(self, attr):
|
|
return os.path.exists(self.dtattrpath(attr))
|
|
|
|
def dtattr(self, attr, default=None):
|
|
if not self.hasdtattr(attr):
|
|
return default
|
|
|
|
return open(self.dtattrpath(attr), "rb").read()
|
|
|
|
def dtstr(self, attr, default=None):
|
|
val = self.dtattr(attr)
|
|
return val.decode("utf-8").strip("\0") if val else default
|
|
|
|
|
|
def vpd_get_json_ve(vpd, pem):
|
|
ves = vpd["data"].get("vendor-extension")
|
|
if not ves:
|
|
return {}
|
|
|
|
out = {}
|
|
for ve in filter(lambda ve: ve[0] == pem, ves):
|
|
out.update(json.loads(ve[1]))
|
|
|
|
return out
|
|
|
|
def vpd_get_pwhash(vpd):
|
|
if not vpd.get("trusted"):
|
|
return None
|
|
|
|
kkit = vpd_get_json_ve(vpd, KKIT_IANA_PEM)
|
|
return kkit.get("pwhash")
|
|
|
|
def vpd_inject(out, vpds):
|
|
out["vpd"] = { vpd["board"]: vpd for vpd in vpds }
|
|
|
|
product = out["vpd"].get("product", {}).get("data", {})
|
|
hoistattrs = ("vendor", "product-name", "part-number", "serial-number", "mac-address")
|
|
for attr in hoistattrs:
|
|
if attr in product:
|
|
out[attr] = product[attr]
|
|
|
|
for vpd in vpds:
|
|
pwhash = vpd_get_pwhash(vpd)
|
|
if pwhash:
|
|
out["factory-password-hash"] = pwhash
|
|
break
|
|
|
|
def qemu_base_mac():
|
|
"""Find MAC address of first non-loopback interface, subtract with 1"""
|
|
base_path = '/sys/class/net'
|
|
interfaces = []
|
|
|
|
for iface in os.listdir(base_path):
|
|
if iface == 'lo':
|
|
continue
|
|
try:
|
|
# pylint: disable=invalid-name
|
|
with open(os.path.join(base_path, iface, 'address'), 'r', encoding='ascii') as f:
|
|
mac = f.read().strip()
|
|
interfaces.append((mac, iface))
|
|
except FileNotFoundError:
|
|
continue
|
|
|
|
if interfaces:
|
|
interfaces.sort()
|
|
mac = interfaces[0][0]
|
|
mac = int(mac.replace(':', ''), 16)
|
|
mac -= 1
|
|
mac %= 1 << 48
|
|
mac = ':'.join(f"{(mac >> 8 * i) & 0xff:02x}" for i in range(5, -1, -1))
|
|
return mac
|
|
|
|
return None
|
|
|
|
def probe_qemusystem(out):
|
|
"""Probe Qemu based test systems and 'make run'"""
|
|
admin_hash = "$5$mI/zpOAqZYKLC2WU$i7iPzZiIjOjrBF3NyftS9CCq8dfYwHwrmUK097Jca9A"
|
|
|
|
qsys = QEMUSystem()
|
|
vpds = qsys.vpds()
|
|
usb_ports = qsys.usb_ports()
|
|
vpd_inject(out, vpds)
|
|
out["usb-ports"] = usb_ports
|
|
for (attr, default) in (
|
|
("vendor", "QEMU"),
|
|
("product-name", "VM"),
|
|
("mac-address", qemu_base_mac()),
|
|
):
|
|
if not out[attr]:
|
|
out[attr] = default
|
|
|
|
if not out["factory-password-hash"] and \
|
|
not out["vpd"]["product"]["available"]:
|
|
# Virtual instance without VPD emulation, fallback to
|
|
# admin/admin
|
|
out["factory-password-hash"] = admin_hash
|
|
|
|
# Let others react to the fact that we are running in QEMU
|
|
subprocess.run("initctl -nbq cond set qemu".split(), check=False)
|
|
return 0
|
|
|
|
def probe_dtsystem(out):
|
|
"""Probe DTS based system, expects a VPD in ONIE PROM format."""
|
|
dtsys = DTSystem()
|
|
vpds = dtsys.infix_vpds()
|
|
dtsys.infix_usb_devices(out)
|
|
model = dtsys.base.str("model")
|
|
if model:
|
|
out["product-name"] = model
|
|
|
|
staticpw = dtsys.infix.str("factory-password-hash")
|
|
if not out["factory-password-hash"]:
|
|
out["factory-password-hash"] = staticpw
|
|
|
|
vpd_inject(out, vpds)
|
|
return 0
|
|
|
|
def main():
|
|
out = {
|
|
"vendor": None,
|
|
"product-name": None,
|
|
"part-number": None,
|
|
"serial-number": None,
|
|
"mac-address": None,
|
|
"factory-password-hash": None,
|
|
"vpd": {}
|
|
}
|
|
vpds = []
|
|
|
|
if os.path.exists(QEMUSystem.REV):
|
|
err = probe_qemusystem(out)
|
|
elif os.path.exists(DTSystem.BASE):
|
|
err = probe_dtsystem(out)
|
|
else:
|
|
return 1
|
|
|
|
if err:
|
|
return err
|
|
|
|
if not out["factory-password-hash"]:
|
|
sys.stdout.write("\n\n\033[31mCRITICAL BOOTSTRAP ERROR\n" +
|
|
"NO FACTORY PASSWORD FOUND\033[0m\n\n")
|
|
err = 1
|
|
|
|
os.umask(0o337)
|
|
# pylint: disable=invalid-name
|
|
with open(SYSTEM_JSON, "w", encoding="ascii") as f:
|
|
json.dump(out, f)
|
|
shutil.chown(SYSTEM_JSON, user="root", group="wheel")
|
|
return err
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|