Skip to content

Commit 43a15b5

Browse files
author
Eugenio Grosso
committed
flasharray: tighten NVMe-TCP EUI-128 validation
Address the 2026-08-04 Copilot follow-up: - FlashArrayVolume.getAddress() now requires the serial to match exactly 24 hexadecimal characters instead of merely rejecting shorter ones. The previous form sliced serial[0:24], so two distinct serials sharing a 24-character prefix would have produced the same EUI-128 and therefore the same volume identity. Non-hex serials are now rejected as well. - FlashArrayAdapter.getVolumeByAddress() validates the FlashArray EUI-128 layout before reversing it into a serial: 32 hexadecimal characters, a 00 prefix, and the Pure Storage OUI at offset 16. Previously any 32-character string was accepted and deterministically mapped onto a volume serial, so a malformed or tampered address could resolve to an unintended volume. Verified against a real FlashArray namespace: EUI 006c1b16ce1c034d24a9371c05ab334a passes both checks and round-trips to serial 6C1B16CE1C034D1C05AB334A and back unchanged. Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com>
1 parent ca9ca20 commit 43a15b5

2 files changed

Lines changed: 31 additions & 8 deletions

File tree

plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Set;
3232
import java.util.concurrent.ConcurrentHashMap;
33+
import java.util.regex.Pattern;
3334

3435
import javax.net.ssl.HostnameVerifier;
3536
import javax.net.ssl.SSLContext;
@@ -93,6 +94,9 @@ public class FlashArrayAdapter implements ProviderAdapter {
9394
private static final String API_LOGIN_VERSION_DEFAULT = "1.19";
9495
private static final String API_VERSION_DEFAULT = "2.23";
9596

97+
/** A FlashArray NVMe namespace EUI-128 is exactly 32 hexadecimal characters. */
98+
private static final Pattern EUI128_PATTERN = Pattern.compile("[0-9a-fA-F]{32}");
99+
96100
// URLs for which the legacy-auth deprecation WARN has already been emitted,
97101
// so we don't spam the logs once per refresh per pool while it's still configured.
98102
private static final Set<String> WARNED_LEGACY_URLS = ConcurrentHashMap.newKeySet();
@@ -353,11 +357,24 @@ public ProviderVolume getVolumeByAddress(ProviderAdapterContext context, Address
353357
// Reverse the EUI-128 layout: serial = eui[2:16] + eui[22:32], after
354358
// stripping the optional "eui." prefix that appears in udev paths.
355359
String eui = address.startsWith("eui.") ? address.substring(4) : address;
356-
if (eui == null || eui.length() != 32) {
360+
if (eui == null || !EUI128_PATTERN.matcher(eui).matches()) {
357361
throw new RuntimeException("Invalid NVMe-TCP EUI-128 address ["
358-
+ address + "]: expected 32 hex characters, got "
362+
+ address + "]: expected 32 hexadecimal characters, got "
359363
+ (eui == null ? "null" : String.valueOf(eui.length())));
360364
}
365+
// Validate the FlashArray EUI-128 layout before deriving a serial from it, so a
366+
// malformed or tampered address cannot be mapped onto an unintended volume:
367+
// 00 + serial[0:14] + <Pure OUI> + serial[14:24]
368+
if (!eui.startsWith("00")) {
369+
throw new RuntimeException("Invalid NVMe-TCP EUI-128 address [" + address
370+
+ "]: expected a \"00\" prefix for a FlashArray namespace");
371+
}
372+
if (!eui.regionMatches(true, 16, FlashArrayVolume.PURE_OUI_EUI, 0,
373+
FlashArrayVolume.PURE_OUI_EUI.length())) {
374+
throw new RuntimeException("Invalid NVMe-TCP EUI-128 address [" + address
375+
+ "]: expected the Pure Storage OUI [" + FlashArrayVolume.PURE_OUI_EUI
376+
+ "] at offset 16");
377+
}
361378
serial = (eui.substring(2, 16) + eui.substring(22)).toUpperCase();
362379
} else {
363380
throw new RuntimeException(

plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayVolume.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// under the License.
1717
package org.apache.cloudstack.storage.datastore.adapter.flasharray;
1818

19+
import java.util.regex.Pattern;
20+
1921
import org.apache.cloudstack.storage.datastore.adapter.ProviderSnapshot;
2022

2123
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -32,6 +34,9 @@ public class FlashArrayVolume implements ProviderSnapshot {
3234
// prefix in its raw 6-hex-digit form.
3335
public static final String PURE_OUI_EUI = "24a937";
3436

37+
/** FlashArray volume serials are exactly 24 hexadecimal characters. */
38+
private static final Pattern SERIAL_PATTERN = Pattern.compile("[0-9a-fA-F]{24}");
39+
3540
@JsonProperty("destroyed")
3641
private Boolean destroyed;
3742
/** The virtual size requested for this volume */
@@ -116,15 +121,16 @@ public String getAddress() {
116121
// 00 + serial[0:14] + <Pure OUI (24a937)> + serial[14:24]
117122
// This is the value the Linux kernel exposes as
118123
// /dev/disk/by-id/nvme-eui.<result>
119-
if (serial.length() < 24) {
124+
// Require an exact 24-hex-character serial. Accepting anything longer and slicing
125+
// the first 24 characters would silently map two distinct serials sharing a prefix
126+
// onto the same EUI, breaking volume identity; accepting anything shorter cannot
127+
// produce a valid 32-character EUI at all.
128+
if (!SERIAL_PATTERN.matcher(serial).matches()) {
120129
throw new RuntimeException("FlashArray serial [" + serial
121-
+ "] is too short to build an NVMe EUI-128 address "
122-
+ "(expected 24 hex characters, got "
130+
+ "] cannot be used to build an NVMe EUI-128 address "
131+
+ "(expected exactly 24 hexadecimal characters, got "
123132
+ serial.length() + ")");
124133
}
125-
// Slice exact ranges rather than substring(14) so a serial with unexpected trailing
126-
// characters cannot produce an EUI longer than 32 hex chars (which would not match
127-
// /dev/disk/by-id/nvme-eui.<eui> on Linux).
128134
return ("00" + serial.substring(0, 14) + PURE_OUI_EUI + serial.substring(14, 24)).toLowerCase();
129135
}
130136
return ("6" + PURE_OUI + serial).toLowerCase();

0 commit comments

Comments
 (0)