CVE-2026-2817 Snapshot Service Configuration
How to use the secure archive extraction code introduced by the CVE-2026-2817 patch in Spring Data Geode.
CVE-2026-2817 Snapshot Service Configuration
The CVE-2026-2817 patch adds a new public setter on SnapshotServiceFactoryBean — setExtractionDirectory(File) — and a matching extraction-directory attribute on the <gfe-data:snapshot-service> XML namespace element. The setter controls the base directory used while extracting a zip/jar snapshot archive during snapshot import. It is the entry point for the hardened extraction code: randomized per-import subdirectory name, owner-only POSIX permissions (or Windows ACLs), and recursive cleanup after import.
The hardened code path only runs when the SnapshotServiceFactoryBean is configured a specific way. A pre-existing Spring Data Geode parser bug — unrelated to this CVE — prevents the <gfe-data:snapshot-service> namespace element from reaching it. This nuance is explianed further in this document.
SnapshotServiceFactoryBean Configuration
When the secure extraction path runs SnapshotServiceFactoryBean picks one of two adapters at bean-creation time based on whether a Region was supplied:
protected SnapshotServiceAdapter create() {
return Optional.ofNullable(getRegion())
.<SnapshotServiceAdapter>map(region -> wrap(region.getSnapshotService()))
.orElseGet(() -> wrap(getCache().getSnapshotService()));
}
| Adapter | Selected. | Behavior on an import |
|---|---|---|
CacheSnapshotServiceAdapter | region is not set, cache is set | If the file matches ArchiveFileFilter (.zip/.jar), enters handleFileLocation — this is the hardened path. Reads extractionDirectory. |
RegionSnapshotServiceAdapter | region is set | Returns the import location unchanged. Never extracts archives. Never reads extractionDirectory. A .zip import will fail because the underlying GemFire RegionSnapshotService.load(...) only accepts .gfd inputs. |
The hardened path is therefore reachable only via a cache-scoped SnapshotServiceFactoryBean importing a zip/jar archive.
XML Namespace Element
The reason the XML configuration does not reach it is due to SnapshotServiceParser.doParse(...) invoking both reference setters unconditionally:
ParsingUtils.setCacheReference(element, builder); // defaults to "gemfireCache" when cache-ref is omitted
ParsingUtils.setRegionReference(element, builder); // always calls addPropertyReference("region", <attr>)
ParsingUtils.setRegionReference always calls BeanDefinitionBuilder.addPropertyReference("region", <region-ref>) regardless of whether region-ref was supplied. When it is omitted, the attribute value is the empty string and Spring rejects the resulting reference at parse time:
java.lang.IllegalArgumentException: 'beanName' must not be empty
So a cache-only XML declaration that should work, like the following:
<gfe-data:snapshot-service id="cacheSnapshotService"
cache-ref="gemfireCache"
extraction-directory="/var/lib/myapp/extract">
<gfe-data:snapshot-import location="/backups/sensitive-snapshot.zip" format="GEMFIRE"/>
</gfe-data:snapshot-service>
fails before any bean is created. Adding region-ref="..." lets the parser succeed but routes the FactoryBean to RegionSnapshotServiceAdapter, whose handleLocation is a passthrough therefore the extractionDirectory is never read.
This is a pre-existing parser issue in upstream Spring Data Geode 2.5.x and 2.7.x. The CVE-2026-2817 patch adds the new attribute and setter but does not modify the parser, so the latent issue is now developer-visible. Use one of the supported configurations below to exercise the hardened extraction code.
Supported configurations
Use one of the following to exercise the hardened extraction code.
1. Java @Bean
@Configuration
class GeodeConfiguration {
@DependsOn(GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME)
@Bean("cacheSnapshotService")
SnapshotServiceFactoryBean<Object, Object> cacheSnapshotService(
Cache cache,
@Value("${snapshot.import.archive}") File importArchive,
@Value("${snapshot.extraction.directory:}") String extractionDir) {
SnapshotServiceFactoryBean<Object, Object> bean = new SnapshotServiceFactoryBean<>();
bean.setCache(cache);
if (!extractionDir.trim().isEmpty()) {
bean.setExtractionDirectory(new File(extractionDir));
}
bean.setImports(new SnapshotMetadata[] {
new SnapshotMetadata<>(importArchive, SnapshotFormat.GEMFIRE)
});
return bean;
}
}
2. Direct XML <bean> declaration
The XML form that works today does not use the <gfe-data:snapshot-service> element at all — it declares the FactoryBean directly:
<bean id="cacheSnapshotService"
class="org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean"
depends-on="gemfireCache">
<property name="cache" ref="gemfireCache"/>
<property name="extractionDirectory" value="/var/lib/myapp/extract"/>
<property name="imports">
<list>
<bean class="org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean$SnapshotMetadata">
<constructor-arg index="0" type="java.io.File" value="/backups/sensitive-snapshot.zip"/>
<constructor-arg index="1" type="org.apache.geode.cache.snapshot.SnapshotOptions$SnapshotFormat" value="GEMFIRE"/>
</bean>
</list>
</property>
</bean>
3. Override java.io.tmpdir at JVM startup
If you cannot modify the application's Spring configuration but can control the JVM invocation, the FactoryBean falls back to the JVM system property java.io.tmpdir whenever extractionDirectory is not set. Overriding that property redirects extraction without any code or XML changes:
java -Djava.io.tmpdir=/var/lib/myapp/extract -jar your-application.jar
or
System.setProperty("java.io.tmpdir", "/var/lib/myapp/extract");
SpringApplication.run(Application.class, args);
Caveats:
- This only helps when the
FactoryBeanis already cache-scoped (Java@Beanor direct<bean>declaration). It does not work around the<gfe-data:snapshot-service>parser failure the bean still fails to construct. java.io.tmpdiris global to the JVM. Every library that callsSystem.getProperty("java.io.tmpdir")orFiles.createTempFile/Directorywithout an explicit base will use the new path. Make sure the target directory is appropriate for general JVM temp use, or prefersetExtractionDirectory(File)for scoped control.- The directory must exist and be writable by the JVM user before startup. Pre-lock it down with the permissions you want (typically
chmod 0700, owned by the service account); theFactoryBeanonly sets permissions on the per-import subdirectories it creates underneath.
Summary for Remediation
| Existing configuration | Status |
|---|---|
<gfe-data:snapshot-service cache-ref="..."> (cache-scoped via namespace) | Did not work in stock either — pre-existing parser bug. No regression introduced by this patch. |
<gfe-data:snapshot-service region-ref="..."> (region-scoped) | Continues to work. Region-scoped imports do not touch the zip-extraction path and are unaffected by CVE-2026-2817. |
@Bean Java config wiring SnapshotServiceFactoryBean (cache-scoped) | Continues to work. Add setExtractionDirectory(File) to opt into the new directory; the rest of the hardening (random subdir, permissions, cleanup) is automatic. |
Direct <bean class="...SnapshotServiceFactoryBean"> (cache-scoped) | Continues to work. Add <property name="extractionDirectory" .../> to opt in. |
Any cache-scoped configuration without extractionDirectory | Hardening still applies under java.io.tmpdir. Override via -Djava.io.tmpdir=... if you cannot edit the configuration. |
References
- Spring Data Geode snapshot reference, 2.7.x: Configuring the Snapshot Service
- Spring Data Geode snapshot reference, 2.5.x: Configuring the Snapshot Service