Skip to main content

hyperlight_common/
version_note.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4//! ELF note types for embedding hyperlight version metadata in guest binaries.
5//!
6//! Guest binaries built with `hyperlight-guest-bin` include a `.note.hyperlight-version`
7//! ELF note section containing the crate version they were compiled against.
8//! The host reads this section at load time to verify ABI compatibility.
9
10/// The ELF note section name used to embed the hyperlight-guest-bin version in guest binaries.
11pub const HYPERLIGHT_VERSION_SECTION: &str = ".note.hyperlight-version";
12
13/// The owner name used in the ELF note header for hyperlight version metadata.
14pub const HYPERLIGHT_NOTE_NAME: &str = "Hyperlight";
15
16/// The note type value used in the ELF note header for hyperlight version metadata.
17pub const HYPERLIGHT_NOTE_TYPE: u32 = 1;
18
19/// Size of the ELF note header (namesz + descsz + type, each u32).
20const NOTE_HEADER_SIZE: usize = 3 * size_of::<u32>();
21
22/// Compute the padded size of the name field for a 64-bit ELF note.
23///
24/// The name must be padded so that the descriptor starts at an 8-byte
25/// aligned offset from the start of the note entry:
26/// `(NOTE_HEADER_SIZE + padded_name) % 8 == 0`.
27pub const fn padded_name_size(name_len_with_nul: usize) -> usize {
28    let desc_offset = NOTE_HEADER_SIZE + name_len_with_nul;
29    let padding = (8 - (desc_offset % 8)) % 8;
30    name_len_with_nul + padding
31}
32
33/// Compute the padded size of the descriptor field for a 64-bit ELF note.
34///
35/// The descriptor must be padded so that the next note entry starts at
36/// an 8-byte aligned offset: `padded_desc % 8 == 0`.
37pub const fn padded_desc_size(desc_len_with_nul: usize) -> usize {
38    let padding = (8 - (desc_len_with_nul % 8)) % 8;
39    desc_len_with_nul + padding
40}
41
42/// An ELF note structure suitable for embedding in a `#[link_section]` static.
43///
44/// Follows the System V gABI note format as specified in
45/// <https://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section>.
46///
47/// `NAME_SZ` and `DESC_SZ` are the **padded** sizes of the name and descriptor
48/// arrays (including null terminator and alignment padding). Use
49/// [`padded_name_size`] and [`padded_desc_size`] to compute them from
50/// `str.len() + 1` (the null-terminated length).
51///
52/// The constructor enforces these constraints with compile-time assertions.
53#[repr(C, align(8))]
54pub struct ElfNote<const NAME_SZ: usize, const DESC_SZ: usize> {
55    namesz: u32,
56    descsz: u32,
57    n_type: u32,
58    // NAME_SZ includes the null terminator and padding to align `desc`
59    // to an 8-byte boundary. Must equal `padded_name_size(namesz)`.
60    // Enforced at compile time by `new()`.
61    name: [u8; NAME_SZ],
62    // DESC_SZ includes the null terminator and padding so the total
63    // note size is a multiple of 8. Must equal `padded_desc_size(descsz)`.
64    // Enforced at compile time by `new()`.
65    desc: [u8; DESC_SZ],
66}
67
68// SAFETY: ElfNote contains only plain data (`u32` and `[u8; N]`).
69// Required because ElfNote is used in a `static` (for `#[link_section]`),
70// and `static` values must be `Sync`.
71unsafe impl<const N: usize, const D: usize> Sync for ElfNote<N, D> {}
72
73impl<const NAME_SZ: usize, const DESC_SZ: usize> ElfNote<NAME_SZ, DESC_SZ> {
74    /// Create a new ELF note from a name string, descriptor string, and type.
75    ///
76    /// # Panics
77    ///
78    /// Panics at compile time if `NAME_SZ` or `DESC_SZ` don't match
79    /// `padded_name_size(name.len() + 1)` or `padded_desc_size(desc.len() + 1)`.
80    pub const fn new(name: &str, desc: &str, n_type: u32) -> Self {
81        // NAME_SZ and DESC_SZ must match the padded sizes.
82        assert!(
83            NAME_SZ == padded_name_size(name.len() + 1),
84            "NAME_SZ must equal padded_name_size(name.len() + 1)"
85        );
86        assert!(
87            DESC_SZ == padded_desc_size(desc.len() + 1),
88            "DESC_SZ must equal padded_desc_size(desc.len() + 1)"
89        );
90
91        // desc must start at an 8-byte aligned offset from the note start.
92        assert!(
93            core::mem::offset_of!(Self, desc).is_multiple_of(8),
94            "desc is not 8-byte aligned"
95        );
96
97        // Total note size must be a multiple of 8 for next-entry alignment.
98        assert!(
99            size_of::<Self>().is_multiple_of(8),
100            "total note size is not 8-byte aligned"
101        );
102
103        Self {
104            namesz: (name.len() + 1) as u32,
105            descsz: (desc.len() + 1) as u32,
106            n_type,
107            name: pad_str_to_array(name),
108            desc: pad_str_to_array(desc),
109        }
110    }
111}
112
113/// Copy a string into a zero-initialised byte array at compile time.
114const fn pad_str_to_array<const N: usize>(s: &str) -> [u8; N] {
115    let bytes = s.as_bytes();
116    let mut result = [0u8; N];
117    let mut i = 0;
118    while i < bytes.len() {
119        result[i] = bytes[i];
120        i += 1;
121    }
122    result
123}