Skip to content

HHH-18455 Strict XML validation compliance #9558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.type.format.FormatMapper;
import org.hibernate.boot.xsd.XmlValidationMode;

import jakarta.persistence.criteria.Nulls;

Expand Down Expand Up @@ -755,6 +756,18 @@ public interface SessionFactoryBuilder {
@Incubating
SessionFactoryBuilder applyXmlFormatMapper(FormatMapper xmlFormatMapper);

/**
* Specifies a {@link XmlValidationMode validation mode} to use for validation of XML files.
*
* @param xmlValidationMode The {@link XmlValidationMode} to use.
*
* @return {@code this}, for method chaining
*
* @see org.hibernate.cfg.AvailableSettings#XML_VALIDATION_MODE
*/
@Incubating
SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode);

/**
* After all options have been set, build the SessionFactory.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.boot.archive.internal;


import org.hibernate.HibernateException;
import org.hibernate.boot.archive.spi.InputStreamAccess;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
* @author Jan Schatteman
*/
public class RepeatableInputStreamAccess implements InputStreamAccess {

private final String resourceName;
private byte[] bytes = new byte[0];

public RepeatableInputStreamAccess(String resourceName, InputStream inputStream) {
if ( inputStream == null ) {
throw new HibernateException( "InputStream is null for resource " + resourceName );
}
this.resourceName = resourceName;
try {
bytes = inputStream.readAllBytes();
}
catch (IOException | OutOfMemoryError e) {
throw new HibernateException( "Could not read resource " + resourceName, e );
}
}

@Override
public String getStreamName() {
return resourceName;
}

@Override
public InputStream accessInputStream() {
return new ByteArrayInputStream( bytes );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.boot.xsd.XmlValidationMode;
import org.hibernate.bytecode.internal.SessionFactoryObserverForBytecodeEnhancer;
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.cache.spi.TimestampsCacheFactory;
Expand Down Expand Up @@ -431,6 +432,12 @@ public void disableJtaTransactionAccess() {
this.optionsBuilder.disableJtaTransactionAccess();
}

@Override
public SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode) {
this.optionsBuilder.applyXmlValidationMode( xmlValidationMode );
return this;
}

@Override
public SessionFactory build() {
return new SessionFactoryImpl( metadata, buildSessionFactoryOptions(), bootstrapContext );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.boot.xsd.XmlValidationMode;
import org.hibernate.cache.internal.NoCachingRegionFactory;
import org.hibernate.cache.internal.StandardTimestampsCacheFactory;
import org.hibernate.cache.spi.RegionFactory;
Expand Down Expand Up @@ -240,6 +241,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {

private final int queryStatisticsMaxSize;

private XmlValidationMode xmlValidationMode;
private final Map<String, Object> defaultSessionProperties;
private final CacheStoreMode defaultCacheStoreMode;
private final CacheRetrieveMode defaultCacheRetrieveMode;
Expand Down Expand Up @@ -538,6 +540,8 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo

defaultLockOptions = defaultLockOptions( defaultSessionProperties );
initialSessionFlushMode = defaultFlushMode( defaultSessionProperties );

xmlValidationMode = ConfigurationHelper.resolveXmlValidationMode( settings );
}

@Deprecated(forRemoval = true)
Expand Down Expand Up @@ -1409,6 +1413,9 @@ public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
return preferJdbcDatetimeTypes;
}

@Override
public XmlValidationMode getXmlValidationMode() { return xmlValidationMode; }

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access

Expand Down Expand Up @@ -1686,7 +1693,6 @@ public void enableGeneratorNameScopeCompliance(boolean enabled) {
mutableJpaCompliance().setGeneratorNameScopeCompliance( enabled );
}


public void enableCollectionInDefaultFetchGroup(boolean enabled) {
this.collectionsInDefaultFetchGroupEnabled = enabled;
}
Expand All @@ -1695,6 +1701,10 @@ public void disableJtaTransactionAccess() {
this.jtaTransactionAccessEnabled = false;
}

public void applyXmlValidationMode(XmlValidationMode xmlValidationMode) {
this.xmlValidationMode = xmlValidationMode;
}

public SessionFactoryOptions buildOptions() {
if ( jpaCompliance instanceof MutableJpaCompliance mutableJpaCompliance ) {
jpaCompliance = mutableJpaCompliance.immutableCopy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
import javax.xml.transform.Source;
import javax.xml.validation.Schema;

import org.hibernate.HibernateException;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.ResourceStreamLocator;
import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess;
import org.hibernate.boot.archive.spi.InputStreamAccess;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.internal.stax.BufferedXMLEventReader;
import org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver;
Expand All @@ -40,62 +43,67 @@
this.xmlResourceResolver = new LocalXmlResourceResolver( resourceStreamLocator );
}

public abstract boolean isValidationEnabled();

@Override
public <X extends T> Binding<X> bind(InputStream stream, Origin origin) {
final XMLEventReader eventReader = createReader( stream, origin );
return bind( new RepeatableInputStreamAccess(origin.getName(), stream), origin );
}

@Override
public <X extends T> Binding<X> bind(InputStreamAccess streamAccess, Origin origin) {
final JaxbBindingSource jaxbBindingSource = createReader( streamAccess, origin );
try {
return doBind( eventReader, origin );
return doBind( jaxbBindingSource );
}
finally {
try {
eventReader.close();
jaxbBindingSource.getEventReader().close();
}
catch (XMLStreamException e) {
log.debug( "Unable to close StAX reader", e );
}
}
}

protected XMLEventReader createReader(InputStream stream, Origin origin) {
try {
// create a standard StAX reader
final XMLEventReader staxReader = staxFactory().createXMLEventReader( stream );
// and wrap it in a buffered reader (keeping 100 element sized buffer)
return new BufferedXMLEventReader( staxReader, 100 );
}
catch ( XMLStreamException e ) {
throw new MappingException( "Unable to create StAX reader", e, origin );
}
protected JaxbBindingSource createReader(InputStreamAccess streamAccess, Origin origin) {
return new JaxbBindingSource() {
@Override
public Origin getOrigin() {
return origin;
}

@Override
public InputStreamAccess getInputStreamAccess() {
return streamAccess;
}

@Override
public XMLEventReader getEventReader() {
try {
// create a standard StAX reader
final XMLEventReader staxReader = staxFactory().createXMLEventReader( streamAccess.accessInputStream() );

Check failure

Code scanning / CodeQL

Resolving XML external entity in user-controlled data Critical

XML parsing depends on a
user-provided value
without guarding against external entity expansion.
// and wrap it in a buffered reader (keeping 100 element sized buffer)
return new BufferedXMLEventReader( staxReader, 100 );
}
catch ( XMLStreamException e ) {
throw new MappingException( "Unable to create StAX reader", e, origin );
}
}
};
}

@Override
public <X extends T> Binding<X> bind(Source source, Origin origin) {
final XMLEventReader eventReader = createReader( source, origin );
return doBind( eventReader, origin );
throw new HibernateException( "The Binder.bind(Source, Origin) method is no longer supported" );
}

protected XMLEventReader createReader(Source source, Origin origin) {
private <X extends T> Binding<X> doBind(JaxbBindingSource jaxbBindingSource) {
try {
// create a standard StAX reader
final XMLEventReader staxReader = staxFactory().createXMLEventReader( source );
// and wrap it in a buffered reader (keeping 100 element sized buffer)
return new BufferedXMLEventReader( staxReader, 100 );
}
catch ( XMLStreamException e ) {
throw new MappingException( "Unable to create StAX reader", e, origin );
}
}

private <X extends T> Binding<X> doBind(XMLEventReader eventReader, Origin origin) {
try {
final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin );
return doBind( eventReader, rootElementStartEvent, origin );
final StartElement rootElementStartEvent = seekRootElementStartEvent( jaxbBindingSource.getEventReader(), jaxbBindingSource.getOrigin() );
return doBind( jaxbBindingSource, rootElementStartEvent );
}
finally {
try {
eventReader.close();
jaxbBindingSource.getEventReader().close();
}
catch (Exception e) {
log.debug( "Unable to close StAX reader", e );
Expand Down Expand Up @@ -138,7 +146,7 @@
return rootElementStartEvent.asStartElement();
}

protected abstract <X extends T> Binding<X> doBind(XMLEventReader staxEventReader, StartElement rootElementStartEvent, Origin origin);
protected abstract <X extends T> Binding<X> doBind(JaxbBindingSource jaxbBindingSource, StartElement rootElementStartEvent);

@SuppressWarnings("unused")
protected static boolean hasNamespace(StartElement startElement) {
Expand All @@ -150,12 +158,7 @@

try {
final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
if ( isValidationEnabled() ) {
unmarshaller.setSchema( xsd );
}
else {
unmarshaller.setSchema( null );
}
unmarshaller.setSchema( xsd );
unmarshaller.setEventHandler( handler );

//noinspection unchecked
Expand All @@ -172,5 +175,4 @@
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.StartElement;
import javax.xml.validation.Schema;

import org.hibernate.Internal;
import org.hibernate.boot.ResourceStreamLocator;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.configuration.spi.JaxbPersistenceImpl;
import org.hibernate.boot.jaxb.internal.stax.ConfigurationEventReader;
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.boot.xsd.ConfigXsdSupport;
import org.hibernate.boot.xsd.XmlValidationMode;
import org.hibernate.internal.util.config.ConfigurationException;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;


/**
* @author Steve Ebersole
*/
Expand All @@ -31,25 +33,33 @@ public ConfigurationBinder(ResourceStreamLocator resourceStreamLocator) {
super( resourceStreamLocator );
}

@Override
public boolean isValidationEnabled() {
return false;
protected XmlValidationMode getXmlValidationMode() {
return XmlValidationMode.DISABLED;
}

@Override
protected <X extends JaxbPersistenceImpl> Binding<X> doBind(
XMLEventReader staxEventReader,
StartElement rootElementStartEvent,
Origin origin) {
final XMLEventReader reader = new ConfigurationEventReader( staxEventReader, xmlEventFactory );
JaxbBindingSource jaxbBindingSource,
StartElement rootElementStartEvent) {
final XMLEventReader reader = new ConfigurationEventReader( jaxbBindingSource.getEventReader(), xmlEventFactory );

final Schema xsd;
// evaluate extended (the former validate_xml 'true') in case anyone should override getXmlValidationMode() to switch it on
if ( getXmlValidationMode() == XmlValidationMode.EXTENDED ) {
xsd = ConfigXsdSupport.configurationXsd().getSchema();
}
else {
xsd = null;
}

final JaxbPersistenceImpl bindingRoot = jaxb(
reader,
ConfigXsdSupport.configurationXsd().getSchema(),
xsd,
jaxbContext(),
origin
jaxbBindingSource.getOrigin()
);
//noinspection unchecked
return new Binding<>( (X) bindingRoot, origin );
return new Binding<>( (X) bindingRoot, jaxbBindingSource.getOrigin() );
}

@Internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ public Binding doBind(Binder binder) {
}

public static Binding doBind(Binder binder, InputStreamAccess inputStreamAccess, Origin origin) {
return inputStreamAccess.fromStream(
inputStream -> binder.bind( inputStream, origin )
);
return binder.bind( inputStreamAccess, origin ) ;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.InputStream;

import org.hibernate.boot.InvalidMappingException;
import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.spi.Binder;
import org.hibernate.boot.jaxb.spi.Binding;
Expand Down Expand Up @@ -38,7 +39,7 @@ public <T> Binding<T> doBind(Binder<T> binder) {

public static <T> Binding<T> doBind(Binder<T> binder, InputStream inputStream, Origin origin, boolean autoClose) {
try {
return binder.bind( inputStream, origin );
return binder.bind( new RepeatableInputStreamAccess( origin.getName(), inputStream), origin );
}
catch ( Exception e ) {
throw new InvalidMappingException( origin, e );
Expand Down
Loading