Skip to content
security

EDK2 PXE Flaw (CVE-2026-97631): Find What You Run

CVE-2026-97631 (CVSS 4.0 9.5) hits EDK II's PXE driver through edk2-stable202608. No vendor firmware fix yet. Find which hosts are yours to fix in an hour.

Cloud Sleuth 13 min read

On October 22, 2026, the TianoCore project published GHSA-99cj-7hg9-mp4m, tracked as CVE-2026-97631. It’s an out-of-bounds write in EDK II’s PXE Base Code driver. While a machine is network booting over IPv4 or IPv6, an unauthenticated attacker who can get an oversized ICMP error message to its IP address can corrupt firmware memory and, if they know the exact firmware build, run code before the operating system loads and before Secure Boot or measured boot has checked anything. The CNA score is CVSS 4.0 9.5. The disclosure was supposed to happen in January, but a public pull request with the fix went up in September, so TianoCore went public early. Ars Technica covered it on October 27.

Why it matters: EDK II is the open source reference code most UEFI firmware is built from. According to Ars, AMI, Insyde and Phoenix all use it heavily, and Microsoft’s Project Mu fork included the vulnerable component. Your servers don’t run EDK II itself. They run firmware some vendor built from it, which means the fix has to come from that vendor, and as of today no firmware vendor or cloud provider has published one.

The question to ask yourself is whether you know which machines have firmware that you, not a cloud provider, are responsible for, and which of those network boot. For EC2 instances and Azure VMs the host firmware belongs to AWS or Microsoft. Your colo racks, your Azure Stack Hub, the lab KVM box and the PXE provisioning network your platform team built in 2019 are yours. If you can list those and name an owner for each, this is a quiet week. If you can’t, start there.

What’s been published, and what hasn’t

Checked October 28, 2026.

Item Status Source
CVE CVE-2026-97631, assigned by TianoCore (its own CNA), published October 22, 2026 CVE record
Component EDK II NetworkPkg/UefiPxeBcDxe, the IPv4 and IPv6 ICMP error handlers (PxeBcIcmpErrorDpcHandle, PxeBcIcmp6ErrorDpcHandle). They copy ICMP errors into a fixed 504-byte field with broken bounds handling GHSA-99cj-7hg9-mp4m
Severity CVSS 4.0 base 9.5 Critical, AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H. NVD hasn’t scored it and lists it as not scheduled for enrichment. Red Hat’s preliminary score is CVSS 3.1 8.8 with Adjacent Network as the attack vector CVE record, NVD, Red Hat
Affected versions EDK II from the start through edk2-stable202608, the latest stable tag (August 21, 2026). The advisory says “EDK II master as of 2026-09-14” CVE record, releases
Upstream fix PR #13302, merged to master on October 15, 2026 (UTC). No stable tag contains it yet, and the advisory’s patched-versions field still reads “Not patched currently” GHSA, Debian tracker
Early public fix PR #13200, opened September 28, 2026 with the same fix. Ars attributes it to AMI Ars Technica
Firmware vendors No entry for this CVE on AMI’s or Insyde’s advisory pages. I couldn’t load Phoenix’s advisory page. Intel told Ars none of its supported commercial products are known to be affected, but OEMs may integrate the code. Arm told Ars it is “not directly applicable” to its portfolio Vendor pages, Ars Technica
Linux packages (OVMF, the EDK II build used for KVM/QEMU guests) Debian: edk2 vulnerable in bookworm, trixie and sid, no fix yet. Ubuntu: needs evaluation on every listed release Debian, Ubuntu
Cloud providers No AWS security bulletin. No entry in Microsoft’s Security Update Guide AWS bulletins page, MSRC API
Exploitation Not in CISA’s KEV catalog. I found no public report of exploitation CISA KEV
Workaround None in the advisory. Ars and CERT/CC’s guidance for the earlier PixieFail bugs in the same package both say to disable PXE where you don’t use it and isolate the networks where you do Ars Technica, CERT/CC VU#132380

Two limits on exploitation are worth knowing. The attack only works during a PXE boot, and Eclypsium’s Jesse Michael told Ars that an exploit has to be tuned to each model, version and configuration because the memory addresses change. A machine that boots from disk and has no network boot entry never reaches the vulnerable code. The advisory also says the packet can come “from any network distance”, but it still has to reach the booting machine’s IP, which in most setups means someone already on your provisioning network.

Where you own the firmware on AWS and Azure

This is the part people get wrong in both directions. Here’s what the providers have documented.

AWS virtual instances and bare-metal instances. According to the Nitro whitepaper, the Nitro Controller verifies the BIOS and all other system firmware before releasing the host CPUs from reset, and the Nitro Security Chip makes sure “the main system CPUs cannot update firmware in bare metal mode.” So on a *.metal instance you can’t flash the host firmware and don’t have to. AWS hasn’t said whether any EC2 firmware contains the affected code.

AWS Outposts. AWS says it manages security patches and updates firmware on Outposts racks. If a firmware update needs a reboot, you get an instance retirement notice.

Amazon EVS. You’re responsible for the ESX hosts’ lifecycle and the VCF stack. AWS handles scheduled maintenance on the EC2 bare-metal instances underneath.

Azure VMs and Dedicated Hosts. Microsoft’s shared responsibility matrix puts physical hosts and the hypervisor on Microsoft for IaaS. On Dedicated Hosts, Maintenance Configurations let you schedule platform updates that don’t need a restart, for up to 35 days, after which they’re applied automatically.

Azure VMware Solution and BareMetal. For AVS, Microsoft manages the VMware stack and replaces failed hosts. That page doesn’t mention host firmware specifically. For Nutanix on Azure BareMetal, Microsoft manages the BareMetal hardware.

Azure Stack Hub. Firmware is your job. Microsoft’s docs say OEM packages carry firmware updates and you, the operator, apply them. So when Dell, HPE, Lenovo or the others ship a fix, someone on your team has to install it.

Anything you run yourself. On-prem and colo servers, self-managed hypervisor hosts (including ones you run on *.metal instances), and the OVMF package your KVM or QEMU hosts hand to their guests are all yours. Your AMIs and VM images don’t carry host firmware, so you don’t need to rebuild them for this.

Neither AWS nor Microsoft has published anything on this CVE yet. Until they do, your job in the cloud is to know which workloads sit on bare metal or dedicated hosts, and to watch for maintenance notices against them.

Every instance type you run tells you where the metal is

Start with an inventory of instance types per region. BareMetal tells you which instances have no hypervisor. Hypervisor separates Nitro from the older Xen families. Bare-metal types may not report a hypervisor at all, so the query prints none for those. describe-regions with no flags only returns regions enabled for your account, and the CLI follows pagination tokens for you.

# Every instance type in use, per enabled region, with bare-metal, hypervisor,
# instance-store and boot-mode fields
for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  types=$(aws ec2 describe-instances --region "$region" \
    --filters Name=instance-state-name,Values=pending,running,stopping,stopped \
    --query "Reservations[].Instances[].InstanceType" --output text | tr '\t' '\n' | sort -u)
  [ -z "$types" ] && continue
  echo "== $region"
  # describe-instance-types takes at most 100 types per call
  echo "$types" | xargs -n 100 aws ec2 describe-instance-types --region "$region" \
    --query "InstanceTypes[].[InstanceType, BareMetal, Hypervisor || 'none', InstanceStorageSupported, to_string(SupportedBootModes)]" \
    --output text --instance-types
done

Then list the bare-metal instances themselves, any Dedicated Hosts, and anything running on an Outpost:

for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  echo "== $region"
  aws ec2 describe-instances --region "$region" \
    --filters "Name=instance-type,Values=*.metal*" \
    --query "Reservations[].Instances[].[InstanceId, InstanceType, State.Name, Placement.Tenancy, Placement.HostId || '-', OutpostArn || '-', Tags[?Key=='Owner'].Value | [0] || 'no-owner-tag']" \
    --output text
  aws ec2 describe-hosts --region "$region" \
    --query "Hosts[].[HostId, HostProperties.InstanceFamily || HostProperties.InstanceType, State, AllocationTime, OutpostArn || '-']" \
    --output text
  aws ec2 describe-instances --region "$region" \
    --filters "Name=outpost-arn,Values=*" \
    --query "Reservations[].Instances[].[InstanceId, InstanceType, OutpostArn]" \
    --output text
done

None of these are instances you patch firmware on. The point is to know which ones are physical machines, because those are the ones that get a host reboot or retirement notice if AWS has to update firmware, and the metal ones are where a team might be running its own hypervisor with OVMF guests. If a bare-metal instance comes back with no-owner-tag, note it now. You don’t want to be asking around for an owner the morning a retirement notice shows up.

On Azure, Resource Graph searches every subscription az can see in a single query, up to 1,000 subscriptions. Run az account list --query "length([?state=='Enabled'])" to see how many that is, and log in to each tenant separately if you have more than one. Results come back 1,000 rows at a time, so this helper follows the skip token:

# Run a Resource Graph query across all visible subscriptions, following skip tokens
arg() {
  local token="" out
  while :; do
    if [ -n "$token" ]; then
      out=$(az graph query -q "$1" --first 1000 --skip-token "$token" -o json)
    else
      out=$(az graph query -q "$1" --first 1000 -o json)
    fi
    echo "$out" | jq -r '.data[] | [.[]] | @tsv'
    token=$(echo "$out" | jq -r '.skip_token // empty')
    [ -z "$token" ] && break
  done
}

# VMs with their size, dedicated host, scale set and owner tag
arg "$(cat <<'KQL'
resources
| where type =~ 'microsoft.compute/virtualmachines'
| project subscriptionId, resourceGroup, name, location,
    size = tostring(properties.hardwareProfile.vmSize),
    dedicatedHost = tostring(properties.host.id),
    scaleSet = tostring(properties.virtualMachineScaleSet.id),
    owner = coalesce(tostring(tags.owner), tostring(tags.Owner), tostring(tags.team), tostring(tags.Team), 'no-owner-tag')
KQL
)"

# Scale sets (Uniform instances don't appear as virtualMachines above)
arg "$(cat <<'KQL'
resources
| where type =~ 'microsoft.compute/virtualmachinescalesets'
| project subscriptionId, resourceGroup, name, location,
    size = tostring(sku.name), capacity = tostring(sku.capacity),
    hostGroup = tostring(properties.hostGroup.id)
KQL
)"

# Dedicated Hosts
arg "$(cat <<'KQL'
resources
| where type =~ 'microsoft.compute/hostgroups/hosts'
| project subscriptionId, resourceGroup, name, location,
    sku = tostring(sku.name), provisioned = tostring(properties.provisioningTime)
KQL
)"

Anything with a non-empty dedicatedHost sits on a physical server that only your tenant uses. Microsoft still owns its firmware, but you’re the one who picks the maintenance window.

The servers you run yourself are the real list

This is where firmware is actually your job. Azure Resource Graph can at least tell you about the hybrid hardware it knows about: Stack Hub registrations, Azure Local clusters and Arc-connected machines. For Arc machines it reports manufacturer and model, which is enough to know which OEM advisory to watch. It doesn’t give you the BIOS version. This uses the arg helper from the previous section.

arg "$(cat <<'KQL'
resources
| where type in~ ('microsoft.azurestack/registrations', 'microsoft.azurestackhci/clusters', 'microsoft.hybridcompute/machines')
| project subscriptionId, resourceGroup, name, type,
    manufacturer = tostring(properties.detectedProperties.manufacturer),
    model = tostring(properties.detectedProperties.model)
KQL
)"

On each Linux host, these are all read-only. /sys doesn’t need root, dmidecode does.

# Who built the firmware, and which version is running
cat /sys/class/dmi/id/sys_vendor /sys/class/dmi/id/product_name \
    /sys/class/dmi/id/bios_vendor /sys/class/dmi/id/bios_version /sys/class/dmi/id/bios_date
sudo dmidecode -s bios-vendor; sudo dmidecode -s bios-version; sudo dmidecode -s bios-release-date
sudo dmidecode -s chassis-asset-tag

# UEFI or legacy boot, and whether a network boot entry is in the boot list
[ -d /sys/firmware/efi ] && echo "booted UEFI" || echo "booted legacy BIOS"
sudo efibootmgr -v | grep -iE 'pxe|ipv4|ipv6|network|mac\('

# What fwupd knows about this device's firmware (lists only, installs nothing)
fwupdmgr get-devices

# On KVM/QEMU hosts: the OVMF build you hand to guests
dpkg-query -W -f='${Package} ${Version}\n' 'ovmf*' 2>/dev/null
rpm -q edk2-ovmf 2>/dev/null

bios_vendor tells you whose advisory to watch. The efibootmgr line matters more than most people think. A server that normally boots from disk but keeps a PXE entry lower in the boot order will try a network boot the day its disk fails or someone changes the order. If you have hosts that PXE boot every time they start, like diskless HPC nodes, stateless hypervisors or anything reimaged by MAAS, Foreman or Ironic, put those at the top of the list. They hit the vulnerable path on every boot.

For machines you can’t log in to, the BMC’s Redfish API reports the same data with plain GET requests:

curl -s -u "$BMC_USER" "https://$BMC/redfish/v1/Systems" | jq -r '.Members[]."@odata.id"'
curl -s -u "$BMC_USER" "https://$BMC/redfish/v1/Systems/<id>" \
  | jq '{Manufacturer, Model, BiosVersion, BootOrder: .Boot.BootOrder, Override: .Boot.BootSourceOverrideTarget}'

Who can send ICMP errors to a booting machine

To exploit this, an attacker has to get an ICMP error to the machine’s IP while it’s PXE booting. On-prem, that comes down to your provisioning network design, and the cloud CLIs can’t see it. Check whether hosts outside the provisioning segment can send ICMP errors into it. Those are ICMPv4 types 3, 11 and 12 and ICMPv6 types 1 to 4. Only filter them at that segment’s edge, though. Blocking ICMPv6 type 2 (Packet Too Big) in general breaks path MTU discovery.

In the cloud, this only matters for the bare-metal and self-managed hypervisor hosts you found above. If any of their guests network boot, see whether ICMP from anywhere can reach them. This checks IPv4 and IPv6, and the Azure query uses the same arg helper:

# Security groups that allow ICMP from 0.0.0.0/0 or ICMPv6 from ::/0, per enabled region
for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  echo "== $region"
  aws ec2 describe-security-groups --region "$region" \
    --query "SecurityGroups[?IpPermissions[?(IpProtocol=='icmp' || IpProtocol=='1' || IpProtocol=='-1') && IpRanges[?CidrIp=='0.0.0.0/0']] || IpPermissions[?(IpProtocol=='icmpv6' || IpProtocol=='58' || IpProtocol=='-1') && Ipv6Ranges[?CidrIpv6=='::/0']]].[GroupId, GroupName, VpcId]" \
    --output text
done
# NSG rules that allow ICMP or any protocol in from the internet, across all subscriptions
arg "$(cat <<'KQL'
resources
| where type =~ 'microsoft.network/networksecuritygroups'
| mv-expand rule = properties.securityRules limit 1000
| extend direction = tostring(rule.properties.direction),
    access = tostring(rule.properties.access),
    protocol = tolower(tostring(rule.properties.protocol)),
    src = tolower(tostring(rule.properties.sourceAddressPrefix)),
    srcs = tolower(tostring(rule.properties.sourceAddressPrefixes))
| where direction =~ 'Inbound' and access =~ 'Allow' and protocol in ('icmp', '*')
| where src in ('*', 'internet', '0.0.0.0/0', '::/0')
    or srcs contains '"*"' or srcs contains '"internet"'
    or srcs contains '"0.0.0.0/0"' or srcs contains '"::/0"'
| project subscriptionId, resourceGroup, nsg = name, rule = tostring(rule.name), protocol, src, srcs
KQL
)"

Network ACLs, Azure Firewall and on-prem firewalls can block traffic these rules allow, so a match tells you where to look, not that something is reachable. An empty result is good news, but it doesn’t cover an attacker who’s already inside the VPC or VNet.

Who owns each host

Tags first. If there aren’t any, look at the CloudFormation stack tag, then CloudTrail. Event history only covers the last 90 days, so for older hosts you need your trail in S3 and an Athena query.

region=us-east-1; id=i-0abc123example
aws ec2 describe-instances --region "$region" --instance-ids "$id" \
  --query "Reservations[].Instances[].Tags[?Key=='aws:cloudformation:stack-name' || Key=='Owner' || Key=='Team' || Key=='aws:autoscaling:groupName'].[Key, Value]" \
  --output text
aws cloudtrail lookup-events --region "$region" \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue="$id" \
  --query "Events[?EventName=='RunInstances' || EventName=='AllocateHosts'].[EventTime, EventName, Username]" \
  --output table

On Azure, the Activity Log has the same 90-day limit and is per subscription, so pass the subscription from the resource ID:

id="/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachines/<vm>"
az monitor activity-log list --subscription "$(echo "$id" | cut -d/ -f3)" \
  --resource-id "$id" --offset 90d \
  --query "[?operationName.value=='Microsoft.Compute/virtualMachines/write'].[eventTimestamp, caller]" -o tsv

On-prem, the cloud APIs can’t help. You need the asset tag from dmidecode, your CMDB, and whoever owns the provisioning network. Check those three against each other, because they’ll disagree.

What breaks when the firmware update lands

Firmware updates on hardware you own mean a reboot, and disabling PXE breaks any workflow that relies on it to reimage or rescue machines. Before you schedule either one, find out what depends on the host. In AWS, see what’s in an Auto Scaling group, what sits behind a load balancer, what has EBS volumes attached, and what maintenance AWS has already scheduled:

for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  echo "== $region"
  aws autoscaling describe-auto-scaling-instances --region "$region" \
    --query "AutoScalingInstances[].[InstanceId, AutoScalingGroupName, LifecycleState]" --output text
  for tg in $(aws elbv2 describe-target-groups --region "$region" --query "TargetGroups[].TargetGroupArn" --output text); do
    aws elbv2 describe-target-health --region "$region" --target-group-arn "$tg" \
      --query "TargetHealthDescriptions[].Target.Id" --output text | tr '\t' '\n' | sed "s|^|$tg  |"
  done
  aws ec2 describe-instances --region "$region" --filters "Name=instance-type,Values=*.metal*" \
    --query "Reservations[].Instances[].[InstanceId, join(',', BlockDeviceMappings[].Ebs.VolumeId)]" --output text
  aws ec2 describe-instance-status --region "$region" --include-all-instances \
    --filters Name=event.code,Values=instance-reboot,system-reboot,system-maintenance,instance-retirement,instance-stop \
    --query "InstanceStatuses[].[InstanceId, Events[0].Code, Events[0].NotBefore, Events[0].Description]" --output text
done

Many metal types have instance store, which the InstanceStorageSupported column from the first loop shows. That data survives a reboot but not a stop or a retirement. On Azure, host-level maintenance Microsoft has scheduled for your Dedicated Hosts and VMs shows up in Resource Graph (again with arg):

arg "$(cat <<'KQL'
maintenanceresources
| where type == 'microsoft.maintenance/updates'
| extend p = parse_json(properties)
| mv-expand d = p.value
| where tostring(d.maintenanceScope) == 'Host'
| project subscriptionId, resource = tolower(name),
    status = tostring(d.properties.plannedMaintenanceStatus), endTime = tostring(d.endTimeUtc)
KQL
)"

On-prem, ask whoever owns each host two things before you change anything: does anything reimage this machine over the network, and what happens to its workload during a reboot? Clusters with quorum, storage nodes and hypervisor hosts with pinned VMs are where a casual reboot turns into an outage.

A one-hour drill

Time your team on this. List every AWS bare-metal instance, Dedicated Host and Outpost, and every Azure Dedicated Host, then put an owner next to each one. List every physical server, Azure Stack Hub and Azure Local cluster you run yourself, with its firmware vendor and BIOS version. Mark which of them have a network boot entry, and which boot over the network every time. Find every KVM or QEMU host and note its OVMF package version. For your provisioning networks, write down who can send traffic into them. Sign up for advisory notifications from each firmware vendor you found. If that took more than an hour, the slow part is the inventory, and the firmware fix will be just as slow when it ships.

If you’d rather not run all these loops by hand, the Cloud Sleuth CLI does the AWS and Azure inventory and ownership part read-only in one pass.

The cloud half is the easy half

For most teams, the cloud part of this is genuinely the provider’s problem. Nitro is built so you can’t touch host firmware even on bare metal, and Azure puts physical hosts on Microsoft. Arguing about whether EC2 is affected won’t help much. What I’d worry about is the hardware that never got into anyone’s cloud inventory: the Stack Hub that came with a project, the colo rack that predates the cloud migration, the lab hypervisor someone set up with the distro’s OVMF and left alone. Firmware advisories show you whether your asset list includes hardware or stops at the cloud console. I’d also take the 9.5 with some salt. Red Hat scores it 8.8 as an adjacent-network bug, and that matches how hard it is to exploit. Turning off PXE where nothing uses it costs nothing and is worth doing this week. You don’t need a firmware rollout to do it.

Where Cloud Sleuth fits

Cloud Sleuth maps your AWS and Azure resources, assigns each one a likely owner, and shows what depends on it, so “which metal instances do we have and who owns them” is one query instead of a loop per region and subscription. The CLI is read-only and runs on your machine or in your VPC. It doesn’t read firmware versions, and it can’t see on-prem servers that aren’t in your cloud accounts, so you’ll still need the host-level commands above. If you want to try it, join the early-access list.

Frequently asked questions

Are AWS EC2 instances and Azure VMs affected by CVE-2026-97631?

Neither AWS nor Microsoft had published anything about it as of October 28, 2026. On both clouds, the host firmware belongs to the provider. AWS’s Nitro design stops you from updating firmware even on bare-metal instances, so there’s nothing for you to flash. Watch the AWS security bulletins and EC2 scheduled events, and on Azure, Service Health and maintenance notifications.

Is there a patch for CVE-2026-97631?

The fix is in EDK II’s master branch as of October 15, 2026, but no stable release contains it yet, and the advisory still says “Not patched currently.” Servers get it only when their firmware vendor or OEM ships a BIOS or UEFI update. For KVM and QEMU guests, you need an updated OVMF package from your distribution.

How do I check if PXE boot is enabled on a Linux server?

Run sudo efibootmgr -v and look for boot entries with PXE, IPv4, IPv6 or a MAC address in the device path. Check where they sit in BootOrder. Through the BMC, Redfish reports Boot.BootOrder on the system resource. Firmware setup is the final word, because some firmware hides network boot options from the OS.

Does Secure Boot protect against CVE-2026-97631?

No. According to the advisory, the code runs before Secure Boot or measured boot has verified or measured any OS component. Keep Secure Boot and the TPM turned on anyway, but they don’t help with this bug. Disabling network boot where you don’t need it and isolating provisioning networks where you do are what actually reduce exposure.

Most of this week’s work is knowing which machines are yours. If you want that answer for your cloud accounts in one read-only pass, join the Cloud Sleuth early-access list.

Cloud Sleuth CLI

Find out who owns your AWS and Azure resources

Get early access to the free Cloud Sleuth CLI. It maps your accounts, puts an owner on every resource, tagged or not, and shows what's safe to remove. It runs on your machine or in your VPC, and nothing leaves your environment.

Related articles