Skip to main content

hyperlight_common/
version_note.rs

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