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}