Ce que voit votre client quand votre VM démarre
Vous vendez des machines virtuelles sous votre propre nom. Un client en allume une, et la première chose à l’écran est le logo de quelqu’un d’autre.
C’est une VM Proxmox d’origine. Rien n’est cassé, et Proxmox ne fait rien de mal : c’est leur produit et leur nom, ils ont écrit le firmware et l’interface où il apparaît, et ils ont le droit de l’y mettre, de la même façon qu’un constructeur de serveurs pose son badge sur la façade du boîtier. Simplement, ce n’est pas le vôtre.
Le logo n’est que le début.
Voici ce que rapporte un invité UEFI d’origine sur le firmware actuel de Proxmox, pve-edk2-firmware 4.2026.08-1, lu depuis l’invité avec dmidecode :
| Où regarde l’invité | Ce qu’il lit | Le nom de qui |
|---|---|---|
| Écran de démarrage | le logo Proxmox | Proxmox |
| Fournisseur du firmware (SMBIOS type 0) | Proxmox distribution of EDK II | Proxmox |
| Version du firmware | 4.2026.08-1 | la version du paquet Proxmox |
| Fabricant du système (type 1) | QEMU | QEMU |
| Nom du produit (type 1) | Standard PC (Q35 + ICH9, 2009) | QEMU |
| Fabricant du châssis (type 3) | QEMU | QEMU |
| Carte mère (type 2) | absente | personne |
| Interface web de gestion | le logo Proxmox, en haut à gauche | Proxmox |
Le logo de démarrage ne s’arrête pas non plus au firmware. Le firmware UEFI transmet l’image qu’il a dessinée au système d’exploitation par une table ACPI appelée BGRT, la Boot Graphics Resource Table, qui existe pour dire « an image was drawn on the screen during boot »1, et le système d’exploitation la redessine sur son propre écran de démarrage. Le Fisher-Price OS (Windows) la place au-dessus de son indicateur de chargement, et Microsoft appelle la BGRT « the standard interface that Windows uses to access the logo »2. Le thème Plymouth par défaut de Fedora fait de même sous Linux3. Un invité sur le firmware de Proxmox affiche donc le logo de Proxmox deux fois avant que quiconque se soit connecté.
Rien de tout cela n’est difficile à changer.
Le garder changé, si.
Le prochain apt full-upgrade en remettra la moitié en place sans rien dire, et la moitié qu’il ne remet pas en place est celle qui devrait vous inquiéter, et c’est le sujet de l’essentiel de ce billet.
La table MSDM de cette transmission n’a rien à voir avec la marque. Elle porte une clé de licence, et la déplacer dans une VM a son propre billet : Déplacer une licence OEM dans une VM Proxmox.
Tout ce qui est sous /usr/share appartient à apt
Une seule règle décide de chaque choix qui suit. Un fichier installé par un paquet est le fichier du paquet. Modifiez-le sur place et la prochaine mise à jour de ce paquet écrase votre modification sans un mot, parce que pour dpkg il ne fait que remettre son propre fichier là où il l’avait laissé.
Chaque élément de marque doit donc vivre quelque part qu’apt ne possède pas. Un nœud Proxmox a trois endroits de ce genre, et chacun coûte quelque chose de différent :
| Où il vit | Comment il y arrive | Survit à une mise à jour | Ce qu’il vous coûte |
|---|---|---|---|
La config de la VM dans /etc/pve | smbios1, et args pour tout le reste | Oui, aucun paquet ne touche à la config | args est réservé à root, et n’apparaît pas dans l’interface |
| Un fichier que dpkg a reçu l’ordre de laisser tranquille | dpkg-divert | Oui, la copie du paquet part sous un nom .distrib | Les mises à jour n’atteignent plus le fichier, ce qui compte quand ce fichier est un firmware |
| Votre propre répertoire, hors de l’arbre des paquets | /usr/local, référencé depuis args | Oui, aucun paquet ne le possède | Vous devez le déposer vous-même sur chaque nœud |
La description que Debian donne d’un détournement est la plus claire : « a way of forcing dpkg not to install a file into its location, but to a diverted location »4. Cela règle l’interface web. Pour le firmware, c’est l’une de deux options, et la plus dangereuse.
Le châssis, c’est une poignée de chaînes et une vérification de droits
SMBIOS est la table qu’une machine utilise pour se décrire : qui l’a fabriquée, quel modèle c’est, son numéro de série, ce que sont la carte et le boîtier5.
dmidecode la lit, chaque outil d’inventaire que vous avez jamais pointé sur un réseau la lit, le panneau d’informations système du Fisher-Price OS (Windows) la lit, et la plupart des contrôles de licence qui décident si un logiciel tournera sur une machine donnée la lisent aussi.
Sur une VM, c’est QEMU qui l’écrit.
D’où QEMU et Standard PC dans la table ci-dessus.
Le type 1 par smbios1
Proxmox expose une seule structure SMBIOS dans la config de la VM : le type 1, System Information, sous le nom smbios16.
Elle prend manufacturer, product, version, serial, sku, family et uuid.
Le piège est dans qemu-server, pas dans la documentation.
Chaque champ sauf uuid doit correspondre à un motif base64, [A-Za-z0-9+\/]+={0,2}, si bien qu’une valeur en clair contenant une espace est refusée d’emblée.
Les vraies chaînes sont encodées en base64 avec base64=1, et qemu-server les décode avant de construire la ligne de commande QEMU7.
L’éditeur SMBIOS de l’interface le fait à chaque enregistrement, avec le commentaire « smbios values can be arbitrary, so encode and mark config as such »8.
En ligne de commande, c’est à vous de le faire :
b() { printf %s "$1" | base64 -w0; }
qm set 9000 --smbios1 "uuid=$(qm config 9000 | sed -n 's/.*uuid=\([0-9a-f-]*\).*/\1/p'),base64=1,\
manufacturer=$(b 'Example Cloud Ltd'),product=$(b 'EC Compute Instance'),\
version=$(b '2026.10'),family=$(b 'General Purpose'),sku=$(b 'ec-gp-4c16g')"
Notez que l’uuid est remis.
C’est l’identité machine de l’invité, et ce que vous passez à --smbios1 est stocké comme l’option entière, alors lisez-le d’abord et gardez-le.
Mettez votre marque sur le modèle, pas sur chaque VM.
Un clone Proxmox reçoit un UUID tout neuf et garde tous les autres champs de smbios1, si bien que chaque clone sort avec sa propre identité et vos chaînes9, à une exception près, plus bas.
Les types 0, 2, 3 et 11 par args
smbios1 s’arrête au type 1.
Le fournisseur du firmware, la carte mère, le châssis et les chaînes OEM passent tous par args, la ligne que Proxmox transmet telle quelle à QEMU, et que sa propre documentation qualifie de « for experts only »6.
L’option -smbios de QEMU prend les champs de chaque type10 :
args: -smbios 'type=0,vendor=Example Cloud Ltd,version=EC-FW 1.0,date=10/04/2026,uefi=on' -smbios 'type=2,manufacturer=Example Cloud Ltd,product=EC Virtual Board,version=1.0' -smbios 'type=3,manufacturer=Example Cloud Ltd,version=1.0,asset=EC-ASSET-123,sku=ec-gp' -smbios 'type=11,value=example-cloud:instance=123'
Démarré sur QEMU avec ces lignes, le dmidecode de l’invité lit :
| Structure | Champ | D’origine | À votre marque |
|---|---|---|---|
| Type 0 | Vendor | Proxmox distribution of EDK II | Example Cloud Ltd |
| Type 0 | Version | 4.2026.08-1 | EC-FW 1.0 |
| Type 1 | Manufacturer | QEMU | Example Cloud Ltd |
| Type 1 | Product Name | Standard PC (Q35 + ICH9, 2009) | EC Compute Instance |
| Type 1 | Family | non renseigné | General Purpose |
| Type 2 | Manufacturer | structure absente | Example Cloud Ltd |
| Type 2 | Product Name | structure absente | EC Virtual Board |
| Type 3 | Manufacturer | QEMU | Example Cloud Ltd |
| Type 3 | Asset Tag | non renseigné | EC-ASSET-123 |
| Type 11 | String 1 | structure absente | example-cloud:instance=123 |
Quatre pièges sont apparus en chemin. Tous les quatre sont silencieux.
Fournir un type 0 fait disparaître « UEFI is supported ».
OVMF n’écrit son propre type 0 que si QEMU n’en a pas fourni ; la boucle qui parcourt les tables de QEMU met NeedSmbiosType0 = FALSE dès qu’elle rencontre un type 011.
Votre chaîne fournisseur remplace celle de Proxmox, et c’est le but.
Mais le type 0 de QEMU remplace aussi les caractéristiques de firmware d’OVMF, et sans uefi=on la ligne « UEFI is supported » disparaît de dmidecode.
Remettez-la avec uefi=on.
Pour les invités UEFI, la chaîne fournisseur a de toute façon une meilleure place, dans le firmware lui-même, plus bas.
Le type de châssis ne peut pas être défini.
Le type 3 de QEMU prend manufacturer, version, serial, asset et sku, et rien d’autre10.
Il reste à Other.
Deux définitions d’un même type fusionnent, et la dernière gagne.
qemu-server place son -smbios type=1 tôt dans la ligne de commande et vos args tout à la fin12, si bien qu’un second -smbios type=1,manufacturer=… dans args ne jette pas le premier : QEMU les a fusionnés champ par champ, le fabricant venait de args, et l’UUID est resté là où smbios1 l’avait mis.
Cela compte pour le quatrième piège.
Les deux moitiés n’ont pas le même propriétaire.
qemu-server vérifie les droits option par option.
smbios1 est rangé avec les options matérielles et exige VM.Config.HWType, tandis que args tombe dans le cas par défaut tout en bas, qui dit « only root can set »13.
La carte mère, le châssis, le fournisseur du firmware et les chaînes OEM sont à vous seul.
Le type 1, non.
Toute personne à qui vous avez donné des droits sur le matériel peut le réécrire, et sur un produit hébergé, cela peut très bien être le client.
Si la chaîne du fabricant compte, définissez-la aussi dans args. La dernière définition gagne.
Le numéro de série est déjà dans la config
L’essentiel de ce qui a sa place dans le type 1 se trouve déjà dans la config de la VM. Le nom fait un bon numéro de série, parce que qemu-server n’y accepte qu’un nom DNS14 : court, imprimable, jamais de virgule, et la seule chose de la VM qu’un client reconnaîtra.
| Champ du type 1 | Tiré de | Pour une VM de 4 cœurs et 16 GiB appelée web-01, étiquetée production;web |
|---|---|---|
| Serial Number | name | web-01 |
| SKU Number | cores × sockets, et memory | ec-4c16g |
| Family | la première entrée de tags | production |
| UUID | l’UUID smbios1 qu’elle a déjà | inchangé |
| Manufacturer, Product | vos propres chaînes fixes | Example Cloud Ltd, EC Compute Instance |
Deux choses restent dehors.
Le nœud change à chaque migration, et vmgenid est fait pour changer : tout son rôle est de dire à l’invité qu’il a été restauré depuis un instantané ou construit à partir d’un modèle15.
Ni l’un ni l’autre n’est une identité.
#!/bin/bash
# /usr/local/sbin/ec-smbios VMID: rebuild smbios1 from the VM's own config.
set -euo pipefail
id=$1
cfg=$(qm config "$id" --current)
get() { sed -n "s/^$1: //p" <<<"$cfg"; }
b() { printf %s "$1" | base64 -w0; }
name=$(get name)
[ -n "$name" ] || { echo "VM $id has no name to use as a serial" >&2; exit 1; }
cores=$(get cores); sockets=$(get sockets); vcpu=$(( ${cores:-1} * ${sockets:-1} ))
mem=$(get memory); mem=${mem#current=}; mem=${mem%%,*}; mem=${mem:-512}
(( mem % 1024 )) && size="${mem}m" || size="$(( mem / 1024 ))g"
tag=$(get tags); tag=${tag%%;*}
uuid=$(get smbios1 | grep -o 'uuid=[0-9a-fA-F-]*' | cut -d= -f2 || true)
uuid=${uuid:-$(cat /proc/sys/kernel/random/uuid)}
s="uuid=$uuid,base64=1,manufacturer=$(b 'Example Cloud Ltd'),product=$(b 'EC Compute Instance')"
s+=",serial=$(b "$name"),sku=$(b "ec-${vcpu}c${size}")"
[ -n "$tag" ] && s+=",family=$(b "$tag")"
qm set "$id" --smbios1 "$s"
Les valeurs par défaut sont celles de qemu-server, un cœur, un socket et 512 MiB16, si bien qu’une config qui les omet obtient quand même un SKU exact.
Lancé contre la config de la table, avec qm remplacé par un script qui la servait, la ligne qu’il a écrite est arrivée à QEMU décodée comme qemu-server la décode, et le dmidecode de l’invité a relu web-01, ec-4c16g, production et l’UUID qu’il avait déjà.
Une VM sans nom est refusée plutôt que de recevoir un numéro de série vide.
La place évidente pour ceci est un hookscript.
C’est la mauvaise.
qemu-server exécute le hook pre-start à l’intérieur du verrou de config qu’il a pris pour démarrer la VM, et construit la ligne de commande QEMU à partir de la config qu’il a chargée avant l’exécution du hook17.
Un hook qui réécrit le numéro de série change le prochain démarrage, pas celui-ci.
Lancez-le donc depuis votre provisionnement, aux quatre moments où ses entrées changent : création, clonage, renommage et redimensionnement.
Le clonage est celui qui mord.
Un clone garde tous les champs de smbios1 sauf l’UUID9, alors sautez cette étape et chaque VM construite à partir de web-template annonce le nom du modèle comme numéro de série.
Le logo de démarrage vit à deux endroits différents
Le logo qu’affiche un invité dépend de son firmware. Les deux firmwares que livre Proxmox le prennent à des endroits complètement différents :
SeaBIOS (bios: seabios, par défaut) | OVMF (bios: ovmf, UEFI) | |
|---|---|---|
| D’où vient le logo | un JPEG que QEMU transmet au démarrage | un bitmap compilé dans le firmware |
| Le fichier de Proxmox | /usr/share/qemu-server/bootsplash.jpg, 640×480 | Logo.bmp, 400×120, 8 bits, intégré à OVMF_CODE_4M*.fd |
| Paquet propriétaire | qemu-server | pve-edk2-firmware-ovmf |
| Le changer par VM | args: -boot splash=… | args qui pointe la VM vers un autre firmware |
| Le changer sans rien recompiler | oui | non |
| Atteint l’OS invité par la BGRT | non | oui |
SeaBIOS : un JPEG sur la ligne de commande
qemu-server met la même option -boot sur chaque VM qu’il démarre, menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg18.
La documentation de QEMU dit que l’image est affichée « when option splash=sp_name is given and menu=on, If firmware/BIOS supports them. Currently Seabios for X86 system support it »10.
OVMF n’en prend rien d’autre que splash-time, qu’il utilise comme délai du menu de démarrage, et il n’y a aucune référence au fichier d’écran de démarrage nulle part dans OvmfPkg19.
Le remplacer tient en une ligne dans la config de la VM :
args: -boot splash=/etc/pve/branding/splash.jpg
Un second -boot ne se bat pas avec le premier.
QEMU le fusionne de la même façon qu’il fusionne -smbios, la dernière valeur gagne, et avec deux fichiers d’écran donnés, la capture d’écran montrait le second.
/etc/pve est la bonne place pour le fichier, parce que c’est le système de fichiers du cluster et que chaque nœud voit le même écran, et sa limite de 1 MiB par fichier est très loin d’un JPEG de 640×48020.
L’image elle-même doit remplir trois conditions, et en rater une vous coûte le logo ou ses couleurs :
| Condition | Pourquoi | Ce qui se passe sinon |
|---|---|---|
| JPEG ou BMP 24 bits | QEMU vérifie le fichier avant le démarrage de la VM | splash file … format not recognized; must be JPEG or 24 bit BMP |
| JPEG baseline, chroma 4:2:0 | le décodeur de SeaBIOS ne gère rien d’autre21 | ERR_NOT_SEQUENTIAL_DCT ou ERR_NOT_YCBCR_221111, et un écran vide |
| 640×480, comme celui de Proxmox | SeaBIOS demande au BIOS VGA un mode exactement à la taille de l’image | pas de mode correspondant, pas d’écran |
La plupart des outils d’image écrivent du baseline 4:2:0 par défaut, alors la façon habituelle de perdre le logo est l’une de ces options « enregistrer pour le web » qui active en douce l’encodage progressif, qui a l’air identique dans toutes vos visionneuses d’images et laisse SeaBIOS ne rien dessiner du tout.
Le piège des couleurs
Celui-là a coûté un après-midi. Le même JPEG, rendu par deux builds de SeaBIOS 1.17.0, sort en deux couleurs différentes :

C’est dans le jpeg.c de SeaBIOS.
Le code d’écriture en 24 bits par pixel a une branche little-endian qui met le bleu dans le premier octet de chaque pixel, et celui en 32 bits par pixel, PIC_32, n’a pas cette branche et y écrit le rouge à la place21.
Sur un framebuffer little-endian, cela échange le rouge et le bleu.
Le bleu sort doré, et l’orange sortirait bleu.
Le code qui s’exécute dépend du mode vidéo que propose le BIOS VGA :
| Firmware | Mode VBE pour 640×480 | Bits par pixel | Couleurs |
|---|---|---|---|
Le SeaBIOS 1.17.0 précompilé de QEMU en amont, tel que pve-qemu-kvm 11.0.3-4 le livre | 0x111 | 16 | justes, quantifiées à 65 536 |
Le build seabios 1.17.0-10 propre à Fedora 44 | 0x142 | 32 | rouge et bleu échangés |
Les numéros de mode viennent de la table de SeaBIOS elle-même22, et la ligne Proxmox a été vérifiée contre les binaires mêmes du paquet : identiques octet pour octet au rel-1.17.0-0-gb52ca86e094d précompilé de QEMU.
Sur Proxmox, vos couleurs sont donc justes, mais il n’y en a que 65 536, et un dégradé subtil fera des bandes.
Et si votre logo apparaît un jour dans les mauvaises couleurs sur un autre hyperviseur, ce n’est pas votre JPEG.
Le logo UEFI est compilé dans le firmware
Les invités UEFI sont la moitié difficile, et sur un Proxmox moderne, ce sont la plupart des invités.
Il n’y a pas d’option d’écran de démarrage à surcharger.
Le logo est un bitmap à l’intérieur de l’image du firmware, et le build de Proxmox l’y met avec une seule ligne dans debian/rules :
debian/setup-build-stamp:
cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp
Cela copie leur Logo.bmp de 400×120 en 8 bits par-dessus celui de TianoCore avant la compilation d’edk223.
Le même fichier fixe le fournisseur du firmware comme constante de compilation, PcdFirmwareVendor=L"Proxmox distribution of EDK II", et c’est de là que vient le fournisseur du type 0 dans la première table23.
Nouveau logo, nouveau firmware.
Pour en obtenir un qui se comporte exactement comme celui de Proxmox, il faut le compiler exactement comme Proxmox le fait : leur arbre au tag qui correspond au paquet qu’ils livrent, leurs patchs, leurs options, et un seul bitmap échangé.
pve-edk2-firmware 4.2026.08-1 fixe edk2 à 2970e56, qui est le tag amont edk2-stable20260823.
git clone https://git.proxmox.com/git/pve-edk2-firmware.git
cd pve-edk2-firmware
git checkout f37039e52d228a7844a90aa2aaf1e161ebd04fcd # 4.2026.08-1
git submodule update --init --recursive
cd edk2
QUILT_PATCHES=../debian/patches quilt push -a
cp /path/to/your/Logo.bmp MdeModulePkg/Logo/Logo.bmp
. ./edksetup.sh && make -C BaseTools
F="-DNETWORK_HTTP_BOOT_ENABLE=TRUE -DNETWORK_IP6_ENABLE=TRUE -DNETWORK_TLS_ENABLE
-DSECURE_BOOT_ENABLE=TRUE -DPVSCSI_ENABLE=TRUE -DTPM2_ENABLE=TRUE
--pcd PcdUninstallMemAttrProtocol=TRUE -DFD_SIZE_4MB"
P=(--pcd "PcdFirmwareVendor=LExample Cloud Ltd\\0"
--pcd "PcdFirmwareVersionString=L4.2026.08-1+ec1\\0")
build -a IA32 -a X64 -t GCC -p OvmfPkg/OvmfPkgIa32X64.dsc $F "${P[@]}" -b RELEASE
cp Build/Ovmf3264/RELEASE_GCC/FV/OVMF_CODE.fd OVMF_CODE_4M.fd
rm -rf Build/Ovmf3264
build -a IA32 -a X64 -t GCC -p OvmfPkg/OvmfPkgIa32X64.dsc $F -DSMM_REQUIRE=TRUE "${P[@]}" -b RELEASE
cp Build/Ovmf3264/RELEASE_GCC/FV/OVMF_CODE.fd OVMF_CODE_4M.secboot.fd
Les options sont reprises de debian/rules tel qu’il est à ce commit.
Attention à la chaîne fournisseur : leur Makefile l’écrit entre guillemets doubles, le shell de make les retire, et edk2 la reçoit nue, c’est donc ainsi qu’elle est passée ici, et la mettre entre guillemets une seconde fois est une erreur de compilation.
Gardez le bitmap en 400×120 et 8 bits comme le leur et rien d’autre ne bouge sur l’écran de démarrage.
Il vous faut les deux images.
qemu-server démarre toute VM Q35 avec un disque EFI 4M sur OVMF_CODE_4M.secboot.fd, le build qui exige SMM, et n’utilise le simple OVMF_CODE_4M.fd que pour i440fx24.
Sur un Proxmox actuel, c’est l’image secboot que fait tourner presque chaque invité UEFI.
Compilée ainsi, sans rien changer à l’arbre de Proxmox sauf le bitmap et deux chaînes, l’image SMM démarre comme ceci :

Et la vue que l’invité a de son propre firmware change avec, sans une seule option -smbios sur la ligne de commande :
| Lu dans l’invité | Le 4.2026.08-1 de Proxmox | Recompilé depuis le même arbre |
|---|---|---|
| Fournisseur du firmware | Proxmox distribution of EDK II | Example Cloud Ltd |
| Version du firmware | 4.2026.08-1 | 4.2026.08-1+ec1 |
| « UEFI is supported » | présent | présent |
| Tables ACPI | BGRT, WSMT et les autres | le même ensemble |
Cela fait du fournisseur défini à la compilation la meilleure réponse pour les invités UEFI.
C’est le propre type 0 d’OVMF, si bien que le bit UEFI reste en place sans que personne ait à se souvenir de uefi=on.
La voie args pour le type 0 est pour les invités SeaBIOS, et pour qui ne recompile pas de firmware du tout.
Deux façons de donner le nouveau firmware à une VM
qemu-server code en dur l’endroit où il cherche le firmware : OVMF.pm contient une table de chemins sous /usr/share/pve-edk2-firmware/, indexée par type de machine et options Secure Boot, et aucune option, nulle part dans la config de la VM, l’interface ou l’API, ne choisit un autre fichier24.
Il reste deux voies.
Voie A : le détourner sur chaque nœud. Dites à dpkg que les deux images de code de Proxmox vont désormais ailleurs, et mettez les vôtres à leur place :
D=/usr/share/pve-edk2-firmware
for f in OVMF_CODE_4M.fd OVMF_CODE_4M.secboot.fd; do
dpkg-divert --package example-branding --add --rename --divert "$D/$f.distrib" "$D/$f"
install -m 0644 "/root/branding/$f" "$D/$f"
done
Dès son prochain démarrage, chaque VM UEFI du nœud démarre sur votre firmware. Aucun changement de config de VM.
Voie B : y pointer les VM choisies.
Gardez les images dans votre propre répertoire et remplacez le firmware une VM à la fois.
Depuis le passage à -blockdev, qemu-server attache l’image de code comme un nœud de bloc appelé pflash0 et le nomme dans -machine24, et QEMU fusionne un second -machine de la même façon qu’il fusionne -boot, si bien que args peut ajouter son propre nœud et y pointer pflash0 :
args: -blockdev driver=raw,node-name=brandcode,read-only=on,file.driver=file,file.filename=/usr/local/share/example-branding/OVMF_CODE_4M.secboot.fd -machine pflash0=brandcode
Cela a été testé par le chemin le plus long.
Le firmware secboot de Proxmox a été attaché comme pflash0 exactement comme qemu-server le fait, avec SMM activé, puis cette ligne args a été ajoutée après, et l’invité a démarré l’image à votre marque avec à la fois le logo et la chaîne fournisseur, et c’est de là que vient la capture d’écran ci-dessus.
| Voie A, détournement | Voie B, args par VM | |
|---|---|---|
| Quelles VM l’obtiennent | chaque VM UEFI du nœud | seulement celles où vous le définissez |
| Les fichiers de Proxmox | déplacés en .distrib | intacts |
| Config de la VM | inchangée | une ligne args, réservée à root |
| Le type de machine doit correspondre | réglé, les deux images sont détournées | à vous de voir : image secboot pour Q35 |
| Annuler | dpkg-divert --remove --rename | supprimer la ligne |
| Migration | le nœud cible doit être détourné aussi | le nœud cible doit avoir le fichier |
L’image de code fait environ 3,5 Mo, face à une limite de 1 MiB par fichier dans pmxcfs20.
De ce fait, elle ne peut pas aller dans /etc/pve, et quelle que soit la voie, elle doit être déposée sur chaque nœud.
Migrez une VM vers un nœud qui ne l’a pas et vous obtenez l’un de deux résultats, selon la voie : le firmware et le logo de Proxmox de retour avec la voie A, ou avec la voie B une VM qui ne démarre pas du tout parce que QEMU ne peut pas ouvrir un fichier qui n’est pas là.
La mise à jour qui vous laisse derrière
Un détournement est le bon outil pour un logo. Le firmware, c’est autre chose.
Un détournement veut dire que les mises à jour de Proxmox n’atteignent plus le fichier. C’est exactement ce que vous avez demandé. C’est aussi exactement ce qui empêche les correctifs de sécurité d’edk2 d’atteindre vos invités : le changelog de Proxmox pour 4.2026.08-1 commence par « Besides many bug and security fixes » et liste ensuite un correctif pour CVE-2024-1374525, et un build à votre marque de la version précédente n’en a rien. Rien ne vous le dit.
Cela a été testé, pas supposé.
Dans un conteneur Debian trixie avec le dépôt de Proxmox, pve-edk2-firmware-ovmf 4.2025.05-3 a été installé, les deux images de code ont été détournées, et le paquet a été mis à jour en 4.2026.08-1 :
| Après la mise à jour | Résultat |
|---|---|
dpkg-query -W pve-edk2-firmware-ovmf | 4.2026.08-1 |
| La nouvelle image de Proxmox | installée sous OVMF_CODE_4M.fd.distrib, empreinte changée |
| L’image à votre marque | inchangée, toujours compilée depuis 4.2025.05-3 |
| Quoi que ce soit à la console à ce sujet | rien, jusqu’au hook ci-dessous |
Ajoutez donc un hook.
apt exécute une liste de commandes DPkg::Post-Invoke après chaque passage de dpkg26.
Notez la version de Proxmox depuis laquelle vous avez compilé, et comparez après chaque passage :
cat > /usr/local/sbin/example-branding-check <<'EOF'
#!/bin/sh
built=$(cat /usr/local/share/example-branding/ovmf.built-from 2>/dev/null)
now=$(dpkg-query -W -f '${Version}' pve-edk2-firmware-ovmf 2>/dev/null)
[ "$built" = "$now" ] && exit 0
echo "W: branded OVMF was built from pve-edk2-firmware-ovmf $built, Proxmox now ships $now." >&2
echo "W: guests still boot the old firmware. Rebuild before the next VM restart." >&2
exit 0
EOF
chmod +x /usr/local/sbin/example-branding-check
echo 'DPkg::Post-Invoke { "/usr/local/sbin/example-branding-check"; };' \
> /etc/apt/apt.conf.d/80example-branding
Sur cette même mise à jour, il a affiché :
W: branded OVMF was built from pve-edk2-firmware-ovmf 4.2025.05-3, Proxmox now ships 4.2026.08-1.
W: guests still boot the old firmware. Rebuild before the next VM restart.
Il sort avec 0 délibérément.
apt s’interrompt si une commande Post-Invoke échoue26, et une vérification de marque n’est pas une raison de laisser un nœud à moitié mis à jour.
Un avertissement bien visible suffit.
Encore une chose. Les redémarrages. Un invité ne prend un nouveau firmware que quand son processus QEMU redémarre, si bien qu’une recompilation demande un arrêt et un démarrage depuis Proxmox plutôt qu’un redémarrage depuis l’invité, et c’est exactement ainsi que les mises à jour de firmware de Proxmox atteignent aussi une VM en marche. Elles n’ont simplement pas besoin que vous pensiez d’abord à recompiler.
Ou laisser le firmware de Proxmox tranquille
Chaque voie vers le logo jusqu’ici change le firmware, et la section précédente en est la facture. Il en existe une qui ne le change pas, et elle a été prototypée pour ce billet.
Un périphérique PCI dans QEMU peut porter une ROM d’option, n’importe quel fichier que vous nommez avec romfile=27, et OVMF exécute le pilote EFI qu’il y trouve.
Le build de Proxmox l’exécute qu’il soit signé ou non : OVMF fixe PcdOptionRomImageVerificationPolicy à 0x00, toujours exécuter28.
OVMF dessine son propre logo tard dans la sélection du périphérique de démarrage29 et ne construit la BGRT qu’à ReadyToBoot, juste avant de passer la main à un chargeur de démarrage.
Et le pilote BGRT d’edk2 accepte une image de remplacement par EDKII_BOOT_LOGO2_PROTOCOL, en reconstruisant la table à ReadyToBoot chaque fois que l’image a changé30.
C’est tout ce qu’il faut.
Le pilote attend ReadyToBoot à TPL_NOTIFY, qui passe avant le gestionnaire TPL_CALLBACK du pilote BGRT, efface l’écran, dessine son logo et transmet la même image.
Compilé, c’est un seul fichier de 8 Ko, attaché avec une seule ligne :
args: -device pci-testdev,romfile=/etc/pve/branding/brandrom.rom
À 8 Ko, il tient largement sous la limite de 1 MiB de pmxcfs20, si bien que contrairement à une image de firmware de 3,5 Mo, il peut vivre dans /etc/pve et suivre la VM sur chaque nœud.
Il a été testé contre le firmware de Proxmox lui-même, tout droit sorti de pve-edk2-firmware-ovmf 4.2026.08-1 : OVMF_CODE_4M.secboot.fd avec les clés de Microsoft pré-enrôlées dans OVMF_VARS_4M.ms.fd, sur Q35 avec SMM, et démarré par le shim signé de Fedora pour que Secure Boot soit actif et appliqué :
| Lu depuis l’invité | Sans la ROM | Avec la ROM |
|---|---|---|
Variable SecureBoot | 1 | 1 |
| À l’écran | le logo de Proxmox | celui de Proxmox pendant environ 250 ms, puis le vôtre |
| Image BGRT, 400×120 en 440,340 | celle de Proxmox, SHA-256 be5afe4b… | celle de la ROM, SHA-256 6c494576… |
| Fichiers du firmware modifiés | aucun | aucun |

La dernière ligne est le but. Le firmware de Proxmox est intact, si bien que leur prochaine version de sécurité atteint vos invités au prochain redémarrage, et rien de la section précédente ne s’applique.
Ce n’est pas gratuit :
| Coût | Pourquoi |
|---|---|
| Le logo de Proxmox s’affiche environ un quart de seconde | OVMF le dessine avant qu’un pilote de ROM d’option ait l’écran ; seule une recompilation l’évite |
| L’invité voit un périphérique PCI de plus, sans pilote | la ROM doit être portée par un périphérique, et pci-testdev est celui de QEMU qui ne fait rien |
| Le type 0 dit toujours Proxmox | la chaîne fournisseur est compilée dedans ; définissez-la par args avec uefi=on, comme plus haut |
| Réservé à root | c’est une ligne args |
Et la découverte qui se cache dessous mérite d’être dite clairement.
Un pilote non signé a tourné dans le firmware d’un invité avec les clés de Microsoft enrôlées et Secure Boot appliqué, parce que l’OVMF de Proxmox ne vérifie pas les ROM d’option.
Ici, c’est utile.
Cela veut aussi dire que sur ce firmware, Secure Boot vérifie les chargeurs de démarrage, pas ce que la config matérielle de la VM y attache, et comme seul root écrit args, c’est une question de qui détient root sur vos nœuds.
C’est un prototype. Il a tourné sur QEMU 10.2.2 avec le firmware de Proxmox, pas encore sur un nœud Proxmox, et ce qui suit est du code source, pas un produit :
github.com/damo2929/RebrandPCIRom : le pilote en ROM d’option, le convertisseur de logo et le build en conteneur.
L’interface web, c’est deux paquets
Le logo en haut à gauche de l’interface web n’est pas dans pve-manager.
Workspace.js demande un composant proxmoxLogoSvg avec le préfixe pwt, et celui-ci vit dans proxmox-widget-toolkit, que partagent le Backup Server et le Mail Gateway31.
Seules les icônes d’onglet sont dans pve-manager :
| Quoi | Fichier | Paquet |
|---|---|---|
| Logo de l’en-tête, dessiné en 200×35 | /usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svg | proxmox-widget-toolkit |
| Icône d’onglet du navigateur | /usr/share/pve-manager/images/favicon.ico | pve-manager |
| Icône 128×128, aussi l’icône tactile | /usr/share/pve-manager/images/logo-128.png | pve-manager |
Ce sont trois fichiers ordinaires, c’est donc un travail pour dpkg-divert, et ici le coût de la section précédente ne s’applique pas du tout, parce qu’un logo n’a aucun correctif de sécurité à manquer et que l’invité de personne n’est moins en sécurité parce qu’un nœud sert encore la favicon du mois dernier.
divert() {
dpkg-divert --package example-branding --add --rename --divert "$1.distrib" "$1"
install -m 0644 "$2" "$1"
}
divert /usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svg /root/branding/logo.svg
divert /usr/share/pve-manager/images/favicon.ico /root/branding/favicon.ico
divert /usr/share/pve-manager/images/logo-128.png /root/branding/logo-128.png
Testé dans le même conteneur, contre de vrais paquets :
| Étape | Résultat |
|---|---|
Détournement sur proxmox-widget-toolkit 5.2.9, installation de votre SVG | le SVG de Proxmox renommé en proxmox_logo.svg.distrib |
| Mise à jour en 5.2.10 | votre SVG toujours en place, la copie 5.2.10 de Proxmox dans .distrib |
dpkg --verify proxmox-widget-toolkit | aucune plainte, dpkg connaît le détournement |
apt-get install --reinstall proxmox-widget-toolkit | votre SVG toujours en place |
dpkg-divert --remove --rename | l’original de Proxmox de retour, octet pour octet |
Deux choses restent à Proxmox.
L’image de l’en-tête est dessinée dans une boîte fixe de 200×35, alors dessinez votre SVG à ce format ou il sera écrasé.
Et le texte alt dit Proxmox et l’image pointe vers https://www.proxmox.com, les deux écrits dans le composant JavaScript plutôt que dans un fichier que vous pouvez détourner31.
Les changer veut dire patcher un bundle minifié qui casse à chaque mise à jour.
Ça n’en vaut pas la peine pour un texte alternatif.
Ce que la licence et la marque de Proxmox vous demandent
Proxmox VE est sous licence AGPL version 332, et la section 13 de cette licence est la clause réseau : « if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network … an opportunity to receive the Corresponding Source of your version »33. Remplacer trois images, est-ce modifier le Programme ? C’est une question pour un juriste. La réponse bon marché est de mettre vos images et le script de détournement quelque part en public et d’y faire un lien depuis la page de connexion. Ça ne coûte rien et ça règle la question.
Côté marque, c’est plus clair. Le kit média de Proxmox dit « Don’t alter the logo or incorporate the logo or symbol into your logo »34. Remplacez donc le leur purement et simplement par le vôtre, jamais par une version recolorée ou retravaillée du leur, et gardez Proxmox hors du nom de votre produit.
Votre nom dessus veut dire votre maintenance
Mettre sa marque sur une VM coûte peu. Une poignée de chaînes SMBIOS, un JPEG, un bitmap et trois images dans une interface web : un après-midi de travail, passé surtout à découvrir où les choses se trouvent.
La garder, c’est le vrai travail. C’est aussi la partie qu’on saute.
Les chaînes SMBIOS et l’écran de démarrage se gardent tout seuls, parce qu’ils vivent dans une config qu’aucun paquet ne touchera jamais, et le logo de l’interface web se garde tout seul parce que dpkg a été mis au courant. Le firmware, non. Le jour où vous mettez votre logo dans une image de firmware, vous reprenez une partie du processus de publication de quelqu’un d’autre : Proxmox compile, teste et livre de nouveaux firmwares avec des correctifs de sécurité dedans, que leurs clients reçoivent à la prochaine mise à jour, et que les vôtres reçoivent quand vous trouvez le temps de recompiler.
Cet échange est acceptable s’il est fait en connaissance de cause. Un hébergeur dont les invités démarrent sur un firmware en retard de deux versions parce que le logo comptait plus que le changelog n’a pas construit un produit. Il a construit un autocollant.
Si votre nom est sur l’écran de démarrage, le firmware derrière est à vous de le tenir à jour, quel qu’en soit l’auteur. Personne ne vérifiera. C’est exactement pour ça qu’il faut le faire.
UEFI Forum, ACPI Specification 6.6, §5.2.23 Boot Graphics Resource Table — « The Boot Graphics Resource Table (BGRT) is an optional table that provides a mechanism to indicate that an image was drawn on the screen during boot ». Copie archivée ; la page en ligne renvoie 403 aux requêtes automatisées. ↩︎
Microsoft Learn, Boot screen components — « This is the standard interface that Windows uses to access the logo. » ↩︎
Fedora Project, Changes/FlickerFreeBoot — « a new plymouth theme which incorporates the firmware’s bootsplash image » ; le plymouth.spec de Fedora fait de
bgrtle thème par défaut. ↩︎Debian, dpkg-divert(1), trixie — « File diversions are a way of forcing dpkg(1) not to install a file into its location, but to a diverted location. » ↩︎
DMTF, DSP0134 System Management BIOS Reference Specification 3.10.0 — type 1 System Information, type 2 carte mère, type 3 « the system’s mechanical enclosure(s) », type 11 « free-form strings defined by the OEM ». ↩︎
Proxmox VE, qm.conf(5) —
smbios1: « Specify SMBIOS type 1 fields » ;args: « Arbitrary arguments passed to kvm … this option is for experts only. » ↩︎ ↩︎qemu-server,
src/PVE/QemuServer.pm, thesmbios1format and command-line builder — chaque champ saufuuida le motif[A-Za-z0-9+\/]+={0,2}; les valeurs sont décodées quandbase64est défini. ↩︎pve-manager,
www/manager6/Parser.js,printQemuSmbios1— « smbios values can be arbitrary, so encode and mark config as such ». ↩︎qemu-server,
src/PVE/API2/Qemu.pm, clone — « auto generate a new uuid », les autres champs desmbios1sont conservés. ↩︎ ↩︎QEMU, System Emulation, Invocation — les champs de
-smbiospar type ;-acpitable« For file=, take whole ACPI table from the specified files, including all ACPI headers » ;-boot« Currently Seabios for X86 system support it. » ↩︎ ↩︎ ↩︎edk2-stable202608,
OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.c— OVMF n’ajoute son propre type 0 que siNeedSmbiosType0est encore vrai après le parcours des tables de QEMU. ↩︎qemu-server,
src/PVE/QemuServer.pm, custom args —argsest découpé et ajouté à la fin de la ligne de commande. ↩︎qemu-server,
src/PVE/API2/Qemu.pm, config permission checks —smbios1est une option de type matériel qui exigeVM.Config.HWType; le cas par défaut « catches args, lock, etc. » et échoue avec « only root can set ». ↩︎qemu-server,
src/PVE/QemuServer.pm, thenameoption —format => 'dns-name'. ↩︎qemu-server,
src/PVE/QemuServer.pm,vmgenid— « notify the guest operating system when the virtual machine is executed with a different configuration (e.g. snapshot execution or creation from a template) ». ↩︎qemu-server,
src/PVE/QemuServer/Memory.pm— mémoiredefault => 512;coresetsocketsvalent 1 par défaut dansQemuServer.pm. ↩︎qemu-server,
src/PVE/QemuServer.pm,vm_start—lock_configà la ligne 5457,exec_hookscript($conf, $vmid, 'pre-start', 1)à 5581, etconfig_to_commandà 5634 avec le même$conf, non rechargé entre les deux. ↩︎qemu-server,
src/PVE/QemuServer.pm,-boot—menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg. ↩︎edk2-stable202608,
OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c— OVMF litetc/boot-menu-wait, la valeur desplash-time, et rien d’autre de-boot. ↩︎pve-cluster,
src/pmxcfs/memdb.h—#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎ ↩︎ ↩︎SeaBIOS,
src/jpeg.c—PICécrit le bleu en premier en little-endian,PIC_32à la ligne 946 écrit le rouge en premier sans branche d’endianness ;ERR_NOT_SEQUENTIAL_DCTetERR_NOT_YCBCR_221111sont les seuls formats refusés. ↩︎ ↩︎SeaBIOS,
vgasrc/svgamodes.c— le mode0x111est du 640×480 en 16 bits,0x142du 640×480 en 32. ↩︎pve-edk2-firmware,
debian/rulesat 4.2026.08-1 —cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp, la chaînePcdFirmwareVendoret les options de build d’OVMF. ↩︎ ↩︎ ↩︎qemu-server,
src/PVE/QemuServer/OVMF.pm— la table de firmwares codée en dur sous/usr/share/pve-edk2-firmware/, le choix SMM et le nœud de blocpflash0. ↩︎ ↩︎ ↩︎pve-edk2-firmware,
debian/changelog— 4.2026.08-1 : « Besides many bug and security fixes … fix CVE-2024-13745 ». ↩︎Debian, apt.conf(5), trixie — « Pre-Invoke, Post-Invoke: This is a list of shell commands to run before/after invoking dpkg(1) … should any fail APT will abort. » ↩︎ ↩︎
QEMU v11.0.3,
hw/pci/pci.c—DEFINE_PROP_STRING("romfile", PCIDevice, romfile), une propriété que possède chaque périphérique PCI. ↩︎edk2-stable202608,
OvmfPkg/OvmfPkgIa32X64.dsc—gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00, le fichier de plateforme que compile Proxmox. ↩︎edk2-stable202608,
OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c—BootLogoEnableLogo (), appelé depuisPlatformBootManagerAfterConsole. ↩︎edk2-stable202608,
BootGraphicsResourceTableDxe.c—SetBootLogo2à la ligne 239 copie l’image ; le gestionnaire ReadyToBoot à 417 désinstalle et réinstalle la table « If BGRT data change happens ». ↩︎proxmox-widget-toolkit,
src/Logo.js—proxmoxLogoSvg, 200×35,alt: 'Proxmox', avec un lien vers proxmox.com ; utilisé depuis leWorkspace.jsde pve-manager avecprefix: 'pwt'. ↩︎ ↩︎Proxmox VE wiki, FAQ — « Proxmox VE code is licensed under the GNU Affero General Public License, version 3. » ↩︎
GNU Affero General Public License v3, §13 Remote Network Interaction — cité en entier dans le texte ci-dessus. ↩︎
Proxmox, Media kit — « Don’t alter the logo or incorporate the logo or symbol into your logo. » ↩︎