Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion flowey/flowey_lib_hvlite/src/_jobs/cfg_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub const GH_CLI: &str = "2.52.0";
pub const MDBOOK: &str = "0.4.40";
pub const MDBOOK_ADMONISH: &str = "1.18.0";
pub const MDBOOK_MERMAID: &str = "0.14.0";
pub const MU_MSVM: &str = "26.0.1";
pub const MU_MSVM: &str = "26.0.3";
pub const NEXTEST: &str = "0.9.101";
pub const NODEJS: &str = "24.x";
// N.B. Kernel version numbers for dev and stable branches are not directly
Expand Down
8 changes: 4 additions & 4 deletions nix/uefi_mu_msvm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ let
else if system == "aarch64-linux" then "AARCH64-CLANGPDB"
else "X64-VS2022";
hash = {
"AARCH64-CLANGPDB" = "sha256-ujHL96/irxRaITtIAxhocbrX+iBQuqNNWDDx8MYQ8i8=";
"X64-VS2022" = "sha256-3NJ4wNA7HXLiMIAVbQXS0cralheCok4rJ8CaedduN9I=";
"AARCH64-CLANGPDB" = "sha256-L1xRlkfek0cajN55neRPnaBjFQnz/G3liZPLzIf2WD4=";
"X64-VS2022" = "sha256-yThByWhaSWNPAdUyBrqzdY1VT/QIzf+yopFumoigajc=";
}.${archToolchain};

in stdenv.mkDerivation {
pname = "uefi-mu-msvm-${archToolchain}";
version = "26.0.1";
version = "26.0.3";

src = fetchzip {
url =
"https://github.com/microsoft/mu_msvm/releases/download/v26.0.1/RELEASE-${archToolchain}-artifacts.tar.gz";
"https://github.com/microsoft/mu_msvm/releases/download/v26.0.3/RELEASE-${archToolchain}-artifacts.tar.gz";
stripRoot = false;
inherit hash;
Comment on lines +17 to 23
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

probably real

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Are the hashes coming from the sha256 entries here?:
https://github.com/microsoft/mu_msvm/releases/tag/v26.0.3

RELEASE-X64-VS2022 is: sha256:d8e320f89f0e4871e5dfac6433dde62c4ac96988a192fcd713f12a96ff6c07a5
RELEASE-AARCH64-CLANGPDB is: sha256:255fbd69f1cf1d3d769d8bad20e1533fe1fcde3436f53bd94d8d387fbbad70f6

The format looks different than what is on this file right now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks like it comes from some nix tool that you can get in linux, but there is some more conversion needed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

right you need to build nix locally otherwise this update will break it. if you're not ready to update this, you should hold off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ran sudo nix-shell --pure twice after emtpying those hash strings. The first one failed for x64 with:

unpacking source archive /build/RELEASE-X64-VS2022-artifacts.tar.gz
error: hash mismatch in fixed-output derivation '/nix/store/01c33fsp6vix5agcqkng26w6lcm22gkk-source.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-yThByWhaSWNPAdUyBrqzdY1VT/QIzf+yopFumoigajc=

And the second time fails for arm64 (after populating x64):

unpacking source archive /build/RELEASE-AARCH64-CLANGPDB-artifacts.tar.gz
error: hash mismatch in fixed-output derivation '/nix/store/lbj91hwmdv458pnp5602j8kl7wi4gj5y-source.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-L1xRlkfek0cajN55neRPnaBjFQnz/G3liZPLzIf2WD4=

These hashes match the state of what's in the PR today, so should be good to go

};
Expand Down
209 changes: 209 additions & 0 deletions nix/update_hashes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env python3

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

"""Helper for updating fetchzip / fetchurl SRI hashes in the .nix files in this folder.

Given one or more URLs, this script invokes ``nix-prefetch-url`` to download
and hash the artifact, then converts the resulting Nix base32 hash into the
SRI format (``sha256-<base64>=``) used by these .nix files.

By default the script passes ``--unpack``, which matches the semantics of
``fetchzip`` (the archive is unpacked and the unpacked tree is hashed). Pass
``--no-unpack`` to hash the artifact bytes directly, which matches the
semantics of ``fetchurl`` (or ``fetchzip { stripRoot = false; }`` over a
non-archive blob).

Usage:
# Prefetch one or more URLs (fetchzip semantics, default).
./update_hashes.py <url> [<url> ...]

# Prefetch one or more URLs without unpacking (fetchurl semantics).
./update_hashes.py --no-unpack <url> [<url> ...]

# Convert already-computed Nix base32 hashes to SRI without re-downloading.
./update_hashes.py --convert <nix32-hash> [<nix32-hash> ...]

Requires ``nix-prefetch-url`` on PATH (``sudo apt install nix-bin`` on Ubuntu /
WSL). On a multi-user Nix install you may need ``sudo`` to access the daemon
socket; in that case prefix the command with ``sudo``.

Example:
sudo ./update_hashes.py \\
https://github.com/microsoft/mu_msvm/releases/download/v26.0.3/RELEASE-X64-VS2022-artifacts.tar.gz \\
https://github.com/microsoft/mu_msvm/releases/download/v26.0.3/RELEASE-AARCH64-CLANGPDB-artifacts.tar.gz
"""

from __future__ import annotations

import argparse
import base64
import re
import shutil
import subprocess
import sys

# Nix base32 alphabet: omits 'e', 'o', 'u', 't' to avoid spelling words.
_NIX32_ALPHABET = "0123456789abcdfghijklmnpqrsvwxyz"

# O(1) char -> 5-bit value lookup, also used to validate input characters.
_NIX32_VALUES = {c: i for i, c in enumerate(_NIX32_ALPHABET)}

# Length in characters of a Nix base32-encoded sha256 hash.
_NIX32_SHA256_LEN = (32 * 8 - 1) // 5 + 1 # = 52

# Matches a standalone Nix base32 sha256 hash: a 52-char run of alphabet chars
# with no surrounding alphanumeric context (so we don't latch onto a substring
# of a store path or other identifier).
_NIX32_SHA256_RE = re.compile(
rf"(?<![0-9a-z])[{_NIX32_ALPHABET}]{{{_NIX32_SHA256_LEN}}}(?![0-9a-z])"
)


def nix32_to_bytes(s: str, hashlen: int = 32) -> bytes:
"""Decode a Nix base32 string (the format printed by ``nix-prefetch-url``)
into its raw bytes.

Nix base32 encodes characters in reverse order vs. position: char ``n`` of
the encoded string holds bits ``[5n .. 5n+4]`` of the hash, but the string
itself is reversed before encoding, so the most-significant bits appear
first when read left-to-right.
"""
expected_len = (hashlen * 8 - 1) // 5 + 1
if len(s) != expected_len:
raise ValueError(
f"unexpected nix32 length {len(s)} for {hashlen}-byte hash "
f"(expected {expected_len})"
)

out = bytearray(hashlen)
# Reverse the string so character index 0 corresponds to the lowest bits.
for n, c in enumerate(reversed(s)):
digit = _NIX32_VALUES.get(c)
if digit is None:
raise ValueError(f"invalid nix32 character: {c!r}")
b = 5 * n
i, j = b // 8, b % 8
out[i] |= (digit << j) & 0xFF
if i + 1 < hashlen:
out[i + 1] |= (digit >> (8 - j)) & 0xFF
return bytes(out)


def nix32_to_sri(nix32: str) -> str:
"""Convert a Nix base32 sha256 hash to SRI (``sha256-<base64>``) format."""
raw = nix32_to_bytes(nix32)
return "sha256-" + base64.b64encode(raw).decode("ascii")


def _extract_hash(stdout: str, url: str) -> str:
"""Find the single Nix base32 sha256 hash in ``nix-prefetch-url`` output.

``nix-prefetch-url`` may print the hash alone, or accompanied by a store
path line (e.g. with ``--print-path``); the exact format also varies
across Nix versions. To be robust we scan all of stdout for tokens
matching the expected hash shape and require exactly one such token.
"""
candidates = _NIX32_SHA256_RE.findall(stdout)
# Filter out any hash-shaped substring of a store path. Store paths look
# like ``/nix/store/<32-char-hash>-name``; the leading hash there is 32
# chars (not 52), so it can't match our regex. But a future Nix could
# plausibly emit something else, so guard explicitly:
candidates = [c for c in candidates if "/nix/store/" not in c]
# Deduplicate while preserving order — some output formats print the hash
# twice (once on its own line, once embedded in a path).
seen: set[str] = set()
unique = [c for c in candidates if not (c in seen or seen.add(c))]
if not unique:
sys.exit(
f"error: could not find a sha256 hash in nix-prefetch-url "
f"output for {url}\n"
f"--- stdout ---\n{stdout}"
)
if len(unique) > 1:
sys.exit(
f"error: found multiple candidate hashes in nix-prefetch-url "
f"output for {url}: {unique}\n"
f"--- stdout ---\n{stdout}"
)
return unique[0]


def prefetch(url: str, *, unpack: bool) -> str:
"""Download ``url`` via ``nix-prefetch-url`` and return the Nix base32 hash.

When ``unpack`` is True, ``--unpack`` is passed (matching ``fetchzip``).
When False, the artifact is hashed verbatim (matching ``fetchurl``).
"""
if shutil.which("nix-prefetch-url") is None:
sys.exit(
"error: nix-prefetch-url not found on PATH.\n"
"Install it with: sudo apt install nix-bin"
)

cmd = ["nix-prefetch-url", "--type", "sha256"]
if unpack:
cmd.append("--unpack")
cmd.append(url)
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
return _extract_hash(result.stdout, url)


def main() -> int:
parser = argparse.ArgumentParser(
description=(
"Prefetch URLs and print SRI hashes suitable for fetchzip / "
"fetchurl in the .nix files in this folder."
)
)
parser.add_argument(
"args",
nargs="*",
metavar="URL_OR_HASH",
help=(
"URLs to prefetch with `nix-prefetch-url`, or, with --convert, "
"Nix base32 hashes to convert to SRI."
),
)
parser.add_argument(
"--convert",
action="store_true",
help=(
"Treat positional arguments as Nix base32 hashes and convert "
"them to SRI without re-downloading."
),
)
parser.add_argument(
"--no-unpack",
dest="unpack",
action="store_false",
default=True,
help=(
"Hash the artifact bytes directly instead of unpacking first. "
"Use this for `fetchurl` (non-archive) sources. Ignored with "
"--convert. Default: --unpack (matches `fetchzip`)."
),
)
parsed = parser.parse_args()

if not parsed.args:
parser.print_help()
return 2

if parsed.convert:
for h in parsed.args:
print(nix32_to_sri(h))
return 0

for url in parsed.args:
print(f"# {url}", file=sys.stderr)
nix32 = prefetch(url, unpack=parsed.unpack)
sri = nix32_to_sri(nix32)
print(sri)

return 0


if __name__ == "__main__":
sys.exit(main())
Loading