mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
feat(xo-server,xo-web): show alert on VMs that are vulnerable to XSA-468 (#8638)
See https://xenbits.xen.org/xsa/advisory-468.html See https://docs.xcp-ng.org/vms/#xsa-468-multiple-windows-pv-driver-vulnerabilities See https://xcp-ng.org/blog/xsa-468-windows-pv-driver-vulnerabilities/
This commit is contained in:
@@ -106,6 +106,7 @@ type BaseXoVm = BaseXapiXo & {
|
||||
videoram?: number
|
||||
viridian: boolean
|
||||
virtualizationMode: DOMAIN_TYPE
|
||||
vulnerabilities: { xsa468: boolean | { reason: string; driver?: string; version?: string } }
|
||||
xenStoreData: Record<string, string>
|
||||
/**
|
||||
* @deprecated use pvDriversVersion instead
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
> Security fixes and new features should go in this section
|
||||
|
||||
- [VM] Detect XSA-468 vulnerable VMs. Read our announcement on the [XCP-ng blog](https://xcp-ng.org/blog/xsa-468-windows-pv-driver-vulnerabilities) for more details.
|
||||
|
||||
### Enhancements
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
@@ -23,10 +25,11 @@
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
- **XO 6**:
|
||||
|
||||
- [VM] Add auto redirection from /vm/[id] to /vm/[id]/console (PR [#8553](https://github.com/vatesfr/xen-orchestra/pull/8553))
|
||||
|
||||
- [Hosts] Avoid getting XO tasks logs flooded with errors on `host.isPubKeyTooShort` (PR [#8605](https://github.com/vatesfr/xen-orchestra/pull/8605))
|
||||
- [VM] Fix "an error has occurred" in Advanced tab when VTPM is `null` (PR [#8601](https://github.com/vatesfr/xen-orchestra/pull/8601))
|
||||
- [Hosts] Avoid getting XO tasks logs flooded with errors on `host.isPubKeyTooShort` (PR [#8605](https://github.com/vatesfr/xen-orchestra/pull/8605))
|
||||
- [VM] Fix "an error has occurred" in Advanced tab when VTPM is `null` (PR [#8601](https://github.com/vatesfr/xen-orchestra/pull/8601))
|
||||
|
||||
### Packages to release
|
||||
|
||||
@@ -50,7 +53,7 @@
|
||||
- @xen-orchestra/rest-api minor
|
||||
- @xen-orchestra/web patch
|
||||
- @xen-orchestra/web-core patch
|
||||
- xo-server patch
|
||||
- xo-web patch
|
||||
- xo-server minor
|
||||
- xo-web minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"blocked-at": "^1.2.0",
|
||||
"bluebird": "^3.5.1",
|
||||
"body-parser": "^1.20.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"complex-matcher": "^0.7.1",
|
||||
"compression": "^1.7.3",
|
||||
"connect-flash": "^0.1.1",
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as xoData from '@xen-orchestra/xapi/xoData.mjs'
|
||||
import ensureArray from './_ensureArray.mjs'
|
||||
import normalizeVmNetworks from './_normalizeVmNetworks.mjs'
|
||||
import semver from 'semver'
|
||||
import { compareVersions, validate as validateVersion } from 'compare-versions'
|
||||
import { createLogger } from '@xen-orchestra/log'
|
||||
import { extractIpFromVmNetworks } from './_extractIpFromVmNetworks.mjs'
|
||||
import { extractProperty, forEach, isEmpty, mapFilter, parseXml } from './utils.mjs'
|
||||
@@ -93,6 +94,82 @@ const getVmGuestToolsProps = vm => {
|
||||
}
|
||||
}
|
||||
|
||||
// ***** May 2025 - Xen Security Advisory XSA-468 - Windows PV drivers vulnerability *****
|
||||
|
||||
// Vendor → (driver → version)
|
||||
const XSA468_VULNERABLE_VERSIONS = {
|
||||
Xen_Project: {
|
||||
xencons: '9.1.0.2',
|
||||
xeniface: '9.1.0.0',
|
||||
xenbus: '9.1.0.1',
|
||||
},
|
||||
Amazon_Inc_: {
|
||||
xeniface: '8.3.0',
|
||||
},
|
||||
XenServer: {
|
||||
xeniface: '9.1.12.93',
|
||||
xenbus: '9.1.11.114',
|
||||
},
|
||||
Citrix: {
|
||||
xeniface: '9.1.1.11',
|
||||
xenbus: '9.1.2.14',
|
||||
},
|
||||
XCP_ng: {
|
||||
xencons: '9.0.9048.9047',
|
||||
xeniface: '9.0.9048.9047',
|
||||
xenbus: '9.0.9048.9047',
|
||||
},
|
||||
}
|
||||
const isVmVulnerable_XSA468 = vm => {
|
||||
const guestMetrics = vm.$guest_metrics
|
||||
|
||||
if (vm.platform?.device_id !== '0002') {
|
||||
// Not a Windows VM
|
||||
return false
|
||||
}
|
||||
|
||||
if (!guestMetrics.PV_drivers_detected) {
|
||||
// No PV drivers installed: no vulnerability
|
||||
return false
|
||||
}
|
||||
|
||||
const pvDriversVersion = guestMetrics.PV_drivers_version
|
||||
let versionDetected = false
|
||||
for (const [key, value] of Object.entries(pvDriversVersion)) {
|
||||
if (['major', 'minor', 'micro', 'build'].includes(key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const [vendor, version] = value.split(' ')
|
||||
if (!validateVersion(version)) {
|
||||
warn(`Invalid version number for ${vendor}: ${key} ${version}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const vendorVulnerableVersion = XSA468_VULNERABLE_VERSIONS[vendor]?.[key]
|
||||
if (vendorVulnerableVersion === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
versionDetected = true
|
||||
|
||||
try {
|
||||
if (compareVersions(version, vendorVulnerableVersion) <= 0) {
|
||||
// PV drivers installed and vulnerable version detected
|
||||
return { reason: 'pv-driver-version-vulnerable', driver: key, version }
|
||||
}
|
||||
} catch (err) {
|
||||
warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
// - PV drivers installed and could check safe versions: no vulnerability
|
||||
// - PV drivers installed but could not check versions: potential vulnerability
|
||||
return versionDetected ? false : { reason: 'no-pv-drivers-detected' }
|
||||
}
|
||||
|
||||
// ***************************************************************************************
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const TRANSFORMS = {
|
||||
@@ -405,6 +482,7 @@ const TRANSFORMS = {
|
||||
isNestedVirtEnabled: semver.satisfies(String(obj.$pool.$master.software_version.platform_version), '>=3.4')
|
||||
? obj.platform['nested-virt'] === 'true'
|
||||
: obj.platform['exp-nested-hvm'] === 'true',
|
||||
vulnerabilities: { xsa468: isVmVulnerable_XSA468(obj) },
|
||||
viridian: obj.platform.viridian === 'true',
|
||||
mainIpAddress: extractIpFromVmNetworks(guestMetrics?.networks),
|
||||
high_availability: obj.ha_restart_priority,
|
||||
|
||||
@@ -496,6 +496,7 @@ const NoObjects = props =>
|
||||
}
|
||||
return acc
|
||||
}, []),
|
||||
vulnerable: Object.values(vm.vulnerabilities).filter(Boolean).length > 0,
|
||||
},
|
||||
{
|
||||
container: { value: containers[vm.$container || vm.$pool] },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'intl'
|
||||
import BulkIcons from 'bulk-icons'
|
||||
import Component from 'base-component'
|
||||
import Ellipsis, { EllipsisContainer } from 'ellipsis'
|
||||
import Icon from 'icon'
|
||||
@@ -65,6 +66,60 @@ export default class VmItem extends Component {
|
||||
(powerState, operations) => (!isEmpty(operations) ? 'Busy' : powerState)
|
||||
)
|
||||
|
||||
_getAlerts = createSelector(
|
||||
() => this.props.item,
|
||||
vm => {
|
||||
const alerts = []
|
||||
|
||||
if (vm.vulnerabilities.xsa468) {
|
||||
const { reason, driver, version } = vm.vulnerabilities.xsa468
|
||||
|
||||
if (reason === 'no-pv-drivers-detected') {
|
||||
alerts.push({
|
||||
level: 'warning',
|
||||
render: (
|
||||
<p>
|
||||
<span>
|
||||
We cannot detect the version of Windows PV drivers on this VM. You may be running an outdated version.
|
||||
Check XCP-ng's{' '}
|
||||
<a href='https://docs.xcp-ng.org/vms/#windows-guest-tools-security' target='_blank' rel='noreferrer'>
|
||||
Windows Guest Tools Security documentation
|
||||
</a>{' '}
|
||||
for more details.
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
Still seeing this message even though you updated PV drivers? Please update your XCP-ng.
|
||||
</p>
|
||||
),
|
||||
})
|
||||
} else {
|
||||
alerts.push({
|
||||
level: 'danger',
|
||||
render: (
|
||||
<p>
|
||||
<span>
|
||||
This VM is running a Windows PV driver vulnerable to XSA-468 ({driver} {version}). You must upgrade
|
||||
your Windows PV drivers now. See{' '}
|
||||
<a
|
||||
href='https://docs.xcp-ng.org/vms/#xsa-468-multiple-windows-pv-driver-vulnerabilities'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
XCP-ng's documentation
|
||||
</a>{' '}
|
||||
for more details.
|
||||
</span>
|
||||
</p>
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return alerts
|
||||
}
|
||||
)
|
||||
|
||||
render() {
|
||||
const { item: vm, container, expandAll, isAdmin, selected } = this.props
|
||||
const resourceSet = this._getResourceSet()
|
||||
@@ -103,6 +158,8 @@ export default class VmItem extends Component {
|
||||
useLongClick
|
||||
/>
|
||||
</Ellipsis>
|
||||
|
||||
<BulkIcons alerts={this._getAlerts()} />
|
||||
</EllipsisContainer>
|
||||
</Col>
|
||||
<Col mediumSize={4} className='hidden-md-down'>
|
||||
|
||||
@@ -55,7 +55,13 @@ import Import from './import'
|
||||
import keymap, { help } from '../keymap'
|
||||
import Tooltip from '../common/tooltip'
|
||||
import { createCollectionWrapper, createGetObjectsOfType } from '../common/selectors'
|
||||
import { bindXcpngLicense, rebindLicense, subscribeXcpngLicenses, subscribeXostorLicenses, subscribeSelfLicenses } from '../common/xo'
|
||||
import {
|
||||
bindXcpngLicense,
|
||||
rebindLicense,
|
||||
subscribeXcpngLicenses,
|
||||
subscribeXostorLicenses,
|
||||
subscribeSelfLicenses,
|
||||
} from '../common/xo'
|
||||
import { SOURCES } from '../common/xoa-plans'
|
||||
import { getLicenseNearExpiration } from '../common/xoa-updater'
|
||||
|
||||
@@ -140,16 +146,20 @@ export const ICON_POOL_LICENSE = {
|
||||
@addSubscriptions({
|
||||
xcpLicenses: subscribeXcpngLicenses,
|
||||
xostorLicenses: subscribeXostorLicenses,
|
||||
selfLicences: subscribeSelfLicenses
|
||||
selfLicences: subscribeSelfLicenses,
|
||||
})
|
||||
@connectStore(state => {
|
||||
const getHosts = createGetObjectsOfType('host')
|
||||
const getXostors = createGetObjectsOfType('SR').filter([sr => sr.SR_type === 'linstor'])
|
||||
const getXsa468VulnerableVms = createGetObjectsOfType('VM').filter([
|
||||
vm => vm.vulnerabilities?.xsa468?.reason === 'pv-driver-version-vulnerable',
|
||||
])
|
||||
return {
|
||||
trial: state.xoaTrialState,
|
||||
registerNeeded: state.xoaUpdaterState === 'registerNeeded',
|
||||
signedUp: !!state.user,
|
||||
hosts: getHosts(state),
|
||||
xsa468VulnerableVms: getXsa468VulnerableVms(state),
|
||||
xostors: getXostors(state),
|
||||
}
|
||||
})
|
||||
@@ -314,7 +324,7 @@ export const ICON_POOL_LICENSE = {
|
||||
})
|
||||
|
||||
return xostorLicenseInfoByXostorId
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class XoApp extends Component {
|
||||
@@ -439,7 +449,7 @@ export default class XoApp extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { signedUp, trial, registerNeeded } = this.props
|
||||
const { signedUp, trial, registerNeeded, xsa468VulnerableVms } = this.props
|
||||
const { pathname } = this.context.router.location
|
||||
const licenseNearExpiration = this.props.selfLicences && getLicenseNearExpiration(this.props.selfLicences, trial)
|
||||
// If we are under expired or unstable trial (signed up only)
|
||||
@@ -448,6 +458,7 @@ export default class XoApp extends Component {
|
||||
(blockXoaAccess(trial) || licenseNearExpiration?.blocked === true) &&
|
||||
!(pathname.startsWith('/xoa/') || pathname === '/backup/restore')
|
||||
const plan = getXoaPlan()
|
||||
|
||||
return (
|
||||
<IntlProvider>
|
||||
<ThemeProvider theme={themes.base}>
|
||||
@@ -502,6 +513,12 @@ export default class XoApp extends Component {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{Object.keys(xsa468VulnerableVms).length > 0 && (
|
||||
<div className='alert alert-danger mb-0'>
|
||||
IMPORTANT! Some of your VMs are vulnerable.{' '}
|
||||
<Link to='/home?s=vulnerable%3F'>Please check immediately.</Link>
|
||||
</div>
|
||||
)}
|
||||
<div style={CONTAINER_STYLE}>
|
||||
<Shortcuts
|
||||
name='XoApp'
|
||||
|
||||
Reference in New Issue
Block a user