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 litLe nom de qui
Écran de démarragele logo ProxmoxProxmox
Fournisseur du firmware (SMBIOS type 0)Proxmox distribution of EDK IIProxmox
Version du firmware4.2026.08-1la version du paquet Proxmox
Fabricant du système (type 1)QEMUQEMU
Nom du produit (type 1)Standard PC (Q35 + ICH9, 2009)QEMU
Fabricant du châssis (type 3)QEMUQEMU
Carte mère (type 2)absentepersonne
Interface web de gestionle logo Proxmox, en haut à gaucheProxmox

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.

Où chaque élément de marque entre dans le démarrageMise sous tensionQEMU construitSMBIOS et ACPIdepuis la configFirmwareOVMF dessine sonlogo intégré,SeaBIOS un écranTransmissiontables SMBIOS,ACPI avec BGRTet MSDMÉcran de l'OSredessine lelogo du firmwaredepuis la BGRTInvité en marchelit les chaînes,l'activation litla clé MSDMconfig de la VMfichier de paquetconfig de la VMfichier de paquetconfig de la VMvit dans /etc/pve, survit à chaque mise à jourvit sous /usr/share, apt le remplace
La marque entre dans le démarrage à cinq endroits. Trois viennent de la config de la VM elle-même et survivent à tout ce que fait apt. Deux viennent de fichiers appartenant à un paquet Proxmox, et ce sont ceux qu’une mise à jour reprend.

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 vitComment il y arriveSurvit à une mise à jourCe qu’il vous coûte
La config de la VM dans /etc/pvesmbios1, et args pour tout le resteOui, aucun paquet ne touche à la configargs est réservé à root, et n’apparaît pas dans l’interface
Un fichier que dpkg a reçu l’ordre de laisser tranquilledpkg-divertOui, la copie du paquet part sous un nom .distribLes 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 argsOui, aucun paquet ne le possèdeVous 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 :

StructureChampD’origineÀ votre marque
Type 0VendorProxmox distribution of EDK IIExample Cloud Ltd
Type 0Version4.2026.08-1EC-FW 1.0
Type 1ManufacturerQEMUExample Cloud Ltd
Type 1Product NameStandard PC (Q35 + ICH9, 2009)EC Compute Instance
Type 1Familynon renseignéGeneral Purpose
Type 2Manufacturerstructure absenteExample Cloud Ltd
Type 2Product Namestructure absenteEC Virtual Board
Type 3ManufacturerQEMUExample Cloud Ltd
Type 3Asset Tagnon renseignéEC-ASSET-123
Type 11String 1structure absenteexample-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 1Tiré dePour une VM de 4 cœurs et 16 GiB appelée web-01, étiquetée production;web
Serial Numbernameweb-01
SKU Numbercores × sockets, et memoryec-4c16g
Familyla première entrée de tagsproduction
UUIDl’UUID smbios1 qu’elle a déjàinchangé
Manufacturer, Productvos propres chaînes fixesExample 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 logoun JPEG que QEMU transmet au démarrageun bitmap compilé dans le firmware
Le fichier de Proxmox/usr/share/qemu-server/bootsplash.jpg, 640×480Logo.bmp, 400×120, 8 bits, intégré à OVMF_CODE_4M*.fd
Paquet propriétaireqemu-serverpve-edk2-firmware-ovmf
Le changer par VMargs: -boot splash=…args qui pointe la VM vers un autre firmware
Le changer sans rien recompilerouinon
Atteint l’OS invité par la BGRTnonoui

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 :

ConditionPourquoiCe qui se passe sinon
JPEG ou BMP 24 bitsQEMU vérifie le fichier avant le démarrage de la VMsplash file … format not recognized; must be JPEG or 24 bit BMP
JPEG baseline, chroma 4:2:0le décodeur de SeaBIOS ne gère rien d’autre21ERR_NOT_SEQUENTIAL_DCT ou ERR_NOT_YCBCR_221111, et un écran vide
640×480, comme celui de ProxmoxSeaBIOS demande au BIOS VGA un mode exactement à la taille de l’imagepas 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 :

Un écran Example Cloud trois fois\u00a0: le JPEG source avec une tuile de logo bleue, SeaBIOS tel que Proxmox le livre avec le même bleu, et un build de SeaBIOS tournant en 32 bits par pixel avec le bleu devenu doré

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 :

FirmwareMode VBE pour 640×480Bits par pixelCouleurs
Le SeaBIOS 1.17.0 précompilé de QEMU en amont, tel que pve-qemu-kvm 11.0.3-4 le livre0x11116justes, quantifiées à 65 536
Le build seabios 1.17.0-10 propre à Fedora 440x14232rouge 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 :

L’écran de démarrage d’un invité dessiné par l’image OVMF recompilée, avec un logo Example Cloud à la place de celui de Proxmox

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 ProxmoxRecompilé depuis le même arbre
Fournisseur du firmwareProxmox distribution of EDK IIExample Cloud Ltd
Version du firmware4.2026.08-14.2026.08-1+ec1
« UEFI is supported »présentprésent
Tables ACPIBGRT, WSMT et les autresle 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.

Compiler un OVMF à votre marque, et deux façons de le donner à une VML'arbre de Proxmoxpve-edk2-firmware 4.2026.08-1Votre marqueLogo.bmp + chaîne fournisseurMême build, mêmes optionsOVMF_CODE_4M.fd + .secboot.fdA : détourner sur chaque nœudchaque VM UEFI le reçoit,aucune config de VM modifiéeB : par VM, via argsau choix, VM par VM,fichiers de Proxmox intactsapt full-upgradele nouveau firmware de Proxmox arrive, le vôtre reste ancienHook Post-Invokeversions différentes, recompilez avant le prochain démarrage
Un seul build, deux voies d’entrée. Dans les deux cas, la prochaine mise à jour installe le nouveau firmware de Proxmox à côté du vôtre et laisse le vôtre tel quel, et c’est pourquoi le hook du bas existe.

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étournementVoie B, args par VM
Quelles VM l’obtiennentchaque VM UEFI du nœudseulement celles où vous le définissez
Les fichiers de Proxmoxdéplacés en .distribintacts
Config de la VMinchangéeune ligne args, réservée à root
Le type de machine doit correspondreréglé, les deux images sont détournéesà vous de voir : image secboot pour Q35
Annulerdpkg-divert --remove --renamesupprimer la ligne
Migrationle nœud cible doit être détourné aussile 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 à jourRésultat
dpkg-query -W pve-edk2-firmware-ovmf4.2026.08-1
La nouvelle image de Proxmoxinstallée sous OVMF_CODE_4M.fd.distrib, empreinte changée
L’image à votre marqueinchangée, toujours compilée depuis 4.2025.05-3
Quoi que ce soit à la console à ce sujetrien, 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 ROMAvec la ROM
Variable SecureBoot11
À l’écranle logo de Proxmoxcelui de Proxmox pendant environ 250 ms, puis le vôtre
Image BGRT, 400×120 en 440,340celle de Proxmox, SHA-256 be5afe4b…celle de la ROM, SHA-256 6c494576…
Fichiers du firmware modifiésaucunaucun

L’image qu’un invité a relue depuis sa table BGRT avec la ROM d’option attachée\u00a0: un logo Example Cloud, légendé drawn by an option ROM

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ûtPourquoi
Le logo de Proxmox s’affiche environ un quart de secondeOVMF 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 pilotela 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 Proxmoxla chaîne fournisseur est compilée dedans ; définissez-la par args avec uefi=on, comme plus haut
Réservé à rootc’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 :

QuoiFichierPaquet
Logo de l’en-tête, dessiné en 200×35/usr/share/javascript/proxmox-widget-toolkit/images/proxmox_logo.svgproxmox-widget-toolkit
Icône d’onglet du navigateur/usr/share/pve-manager/images/favicon.icopve-manager
Icône 128×128, aussi l’icône tactile/usr/share/pve-manager/images/logo-128.pngpve-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 :

ÉtapeRésultat
Détournement sur proxmox-widget-toolkit 5.2.9, installation de votre SVGle SVG de Proxmox renommé en proxmox_logo.svg.distrib
Mise à jour en 5.2.10votre SVG toujours en place, la copie 5.2.10 de Proxmox dans .distrib
dpkg --verify proxmox-widget-toolkitaucune plainte, dpkg connaît le détournement
apt-get install --reinstall proxmox-widget-toolkitvotre SVG toujours en place
dpkg-divert --remove --renamel’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.


  1. 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. ↩︎

  2. Microsoft Learn, Boot screen components — « This is the standard interface that Windows uses to access the logo. » ↩︎

  3. Fedora Project, Changes/FlickerFreeBoot — « a new plymouth theme which incorporates the firmware’s bootsplash image » ; le plymouth.spec de Fedora fait de bgrt le thème par défaut. ↩︎

  4. 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. » ↩︎

  5. 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 ». ↩︎

  6. Proxmox VE, qm.conf(5) — smbios1 : « Specify SMBIOS type 1 fields » ; args : « Arbitrary arguments passed to kvm … this option is for experts only. » ↩︎ ↩︎

  7. qemu-server, src/PVE/QemuServer.pm, the smbios1 format and command-line builder — chaque champ sauf uuid a le motif [A-Za-z0-9+\/]+={0,2} ; les valeurs sont décodées quand base64 est défini. ↩︎

  8. pve-manager, www/manager6/Parser.js, printQemuSmbios1 — « smbios values can be arbitrary, so encode and mark config as such ». ↩︎

  9. qemu-server, src/PVE/API2/Qemu.pm, clone — « auto generate a new uuid », les autres champs de smbios1 sont conservés. ↩︎ ↩︎

  10. QEMU, System Emulation, Invocation — les champs de -smbios par type ; -acpitable « For file=, take whole ACPI table from the specified files, including all ACPI headers » ; -boot « Currently Seabios for X86 system support it. » ↩︎ ↩︎ ↩︎

  11. edk2-stable202608, OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.c — OVMF n’ajoute son propre type 0 que si NeedSmbiosType0 est encore vrai après le parcours des tables de QEMU. ↩︎

  12. qemu-server, src/PVE/QemuServer.pm, custom args — args est découpé et ajouté à la fin de la ligne de commande. ↩︎

  13. qemu-server, src/PVE/API2/Qemu.pm, config permission checks — smbios1 est une option de type matériel qui exige VM.Config.HWType ; le cas par défaut « catches args, lock, etc. » et échoue avec « only root can set ». ↩︎

  14. qemu-server, src/PVE/QemuServer.pm, the name option — format => 'dns-name'. ↩︎

  15. 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) ». ↩︎

  16. qemu-server, src/PVE/QemuServer/Memory.pm — mémoire default => 512 ; cores et sockets valent 1 par défaut dans QemuServer.pm. ↩︎

  17. qemu-server, src/PVE/QemuServer.pm, vm_start — lock_config à la ligne 5457, exec_hookscript($conf, $vmid, 'pre-start', 1) à 5581, et config_to_command à 5634 avec le même $conf, non rechargé entre les deux. ↩︎

  18. qemu-server, src/PVE/QemuServer.pm, -boot — menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg. ↩︎

  19. edk2-stable202608, OvmfPkg/Library/QemuBootOrderLib/QemuBootOrderLib.c — OVMF lit etc/boot-menu-wait, la valeur de splash-time, et rien d’autre de -boot. ↩︎

  20. pve-cluster, src/pmxcfs/memdb.h — #define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB. ↩︎ ↩︎ ↩︎

  21. 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_DCT et ERR_NOT_YCBCR_221111 sont les seuls formats refusés. ↩︎ ↩︎

  22. SeaBIOS, vgasrc/svgamodes.c — le mode 0x111 est du 640×480 en 16 bits, 0x142 du 640×480 en 32. ↩︎

  23. pve-edk2-firmware, debian/rules at 4.2026.08-1 — cp -a debian/Logo.bmp MdeModulePkg/Logo/Logo.bmp, la chaîne PcdFirmwareVendor et les options de build d’OVMF. ↩︎ ↩︎ ↩︎

  24. 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 bloc pflash0. ↩︎ ↩︎ ↩︎

  25. pve-edk2-firmware, debian/changelog — 4.2026.08-1 : « Besides many bug and security fixes … fix CVE-2024-13745 ». ↩︎

  26. 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. » ↩︎ ↩︎

  27. 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. ↩︎

  28. edk2-stable202608, OvmfPkg/OvmfPkgIa32X64.dsc — gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x00, le fichier de plateforme que compile Proxmox. ↩︎

  29. edk2-stable202608, OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c — BootLogoEnableLogo (), appelé depuis PlatformBootManagerAfterConsole. ↩︎

  30. 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 ». ↩︎

  31. proxmox-widget-toolkit, src/Logo.js — proxmoxLogoSvg, 200×35, alt: 'Proxmox', avec un lien vers proxmox.com ; utilisé depuis le Workspace.js de pve-manager avec prefix: 'pwt'. ↩︎ ↩︎

  32. Proxmox VE wiki, FAQ — « Proxmox VE code is licensed under the GNU Affero General Public License, version 3. » ↩︎

  33. GNU Affero General Public License v3, §13 Remote Network Interaction — cité en entier dans le texte ci-dessus. ↩︎

  34. Proxmox, Media kit — « Don’t alter the logo or incorporate the logo or symbol into your logo. » ↩︎