public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Daniel Kral <d.kral@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [RFC PATCH installer] auto: allow a binary executable as the first boot executable
Date: Fri,  6 Dec 2024 14:24:55 +0100	[thread overview]
Message-ID: <20241206132455.145355-1-d.kral@proxmox.com> (raw)

As the initial use case for the first boot feature request [0] was for
running shell scripts, the auto installer retrieved the binary as a
`String`. Unfortunately, this tries to interpret binary data as UTF-8
and will transform 'invalid' characters to replacement characters [1].

This causes the auto-installer to create an invalid binary when fetching
from an URL, which will likely cause a segmentation fault and the binary
to be never removed by the systemd target, and error with a `stream did
not contain valid UTF-8` when fetching from the ISO image.

To allow binary executables to be used as a first boot executable, this
commit changes the fetching from being read as a `String` to being read
as a vector of bytes `Vec<u8>` to not interfere with the content of a
binary executable.

[0] https://bugzilla.proxmox.com/show_bug.cgi?id=5579
[1] https://doc.rust-lang.org/src/alloc/string.rs.html#635

Signed-off-by: Daniel Kral <d.kral@proxmox.com>
---
To verify the former, I've created two simple test executables to verify
that both were executed as expected:

```
#include <syslog.h>

int main() {
	int logprio = LOG_USER | LOG_INFO;
        syslog(logprio, "Hello from first-boot!");
        return 0;
}
```

```
#!/bin/bash

logger <<EOF
Hello from the first-boot script!
EOF
```

As described in the commit message, with an unpatched auto-installer,
the binary will fail to run when fetched from an URL or fail the
installation when fetched from the ISO image. The shell script works as
expected.

After applying the patch when booting the install ISO in debug mode, the
auto-installer correctly writes and execute the binary when fetched from
an URL (both HTTP and HTTPS) and the same works when fetched from ISO.

I've also tested two common error scenarios: When the HTTP is offline,
there's an human-readable error that the connection was refused (e.g.
host is online, cannot establish a connection). When the HTTP response
is missing the "Content-Length" header, it will be explicitly stated in
the error message.

 .../src/bin/proxmox-auto-installer.rs         | 12 ++++++---
 proxmox-installer-common/src/http.rs          | 25 +++++++++++++++----
 2 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
index ece9a94..408fc0e 100644
--- a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
+++ b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs
@@ -38,14 +38,18 @@ fn setup_first_boot_executable(first_boot: &FirstBootHookInfo) -> Result<()> {
         FirstBootHookSourceMode::FromUrl => {
             if let Some(url) = &first_boot.url {
                 info!("Fetching first-boot hook from {url} ..");
-                Some(http::get(url, first_boot.cert_fingerprint.as_deref())?)
+                Some(http::get_as_bytes(
+                    url,
+                    first_boot.cert_fingerprint.as_deref(),
+                    FIRST_BOOT_EXEC_MAX_SIZE,
+                )?)
             } else {
                 bail!("first-boot hook source set to URL, but none specified!");
             }
         }
-        FirstBootHookSourceMode::FromIso => Some(fs::read_to_string(format!(
-            "/cdrom/{FIRST_BOOT_EXEC_NAME}"
-        ))?),
+        FirstBootHookSourceMode::FromIso => {
+            Some(fs::read(format!("/cdrom/{FIRST_BOOT_EXEC_NAME}"))?)
+        }
     };
 
     if let Some(content) = content {
diff --git a/proxmox-installer-common/src/http.rs b/proxmox-installer-common/src/http.rs
index f4afe14..a748266 100644
--- a/proxmox-installer-common/src/http.rs
+++ b/proxmox-installer-common/src/http.rs
@@ -1,6 +1,7 @@
-use anyhow::Result;
+use anyhow::{bail, Result};
 use rustls::ClientConfig;
 use sha2::{Digest, Sha256};
+use std::io::Read;
 use std::sync::Arc;
 use ureq::{Agent, AgentBuilder};
 
@@ -53,12 +54,26 @@ fn build_agent(fingerprint: Option<&str>) -> Result<Agent> {
 /// # Arguments
 /// * `url` - URL to fetch
 /// * `fingerprint` - SHA256 cert fingerprint if certificate pinning should be used. Optional.
-pub fn get(url: &str, fingerprint: Option<&str>) -> Result<String> {
-    Ok(build_agent(fingerprint)?
+/// * `max_size` - Maximum amount of bytes that will be read.
+pub fn get_as_bytes(url: &str, fingerprint: Option<&str>, max_size: usize) -> Result<Vec<u8>> {
+    let mut result: Vec<u8> = Vec::new();
+
+    let response = build_agent(fingerprint)?
         .get(url)
         .timeout(std::time::Duration::from_secs(60))
-        .call()?
-        .into_string()?)
+        .call()?;
+
+    match response
+        .into_reader()
+        .take(max_size as u64)
+        .read_to_end(&mut result)
+    {
+        Err(ref err) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
+            bail!("Unexpected end of line. Does the HTTP server provide a Content-Length header?")
+        }
+        Err(err) => bail!("Error while reading GET request: {err}"),
+        Ok(_) => Ok(result),
+    }
 }
 
 /// Issues a POST request with the payload (JSON). Optionally a SHA256 fingerprint can be used to
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


             reply	other threads:[~2024-12-06 13:25 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-12-06 13:24 Daniel Kral [this message]
2024-12-09  9:57 ` Christoph Heiss
2024-12-11  9:01   ` Daniel Kral

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20241206132455.145355-1-d.kral@proxmox.com \
    --to=d.kral@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal