getTrustedClasses() {
- return trustedClasses;
+ return Arrays.asList(SERIALIZABLE_CLASSES);
}
@Override
diff --git a/lang/java/avro/src/main/java/org/apache/avro/util/ClassSecurityValidator.java b/lang/java/avro/src/main/java/org/apache/avro/util/ClassSecurityValidator.java
new file mode 100644
index 00000000000..b50d0f0250a
--- /dev/null
+++ b/lang/java/avro/src/main/java/org/apache/avro/util/ClassSecurityValidator.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro.util;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Validates that a class is trusted to be included in Avro schemas. To be used
+ * by {@link ClassUtils} which therefore automatically guards not only the
+ * loading of the classes but, since the class names are translated into
+ * {@link Class} objects by using {@link ClassUtils}, also guards any other
+ * reflection-based mechanisms (e.g. instantiation, setting internal variables).
+ *
+ * @see #setGlobal(ClassSecurityPredicate)
+ * @see #getGlobal()
+ */
+public final class ClassSecurityValidator {
+
+ /**
+ * Validates that the class is trusted to be included in Avro schemas.
+ *
+ *
+ * Note: this method shall be invoked with un-initialized classes only to
+ * prevent any potential security issues the initialization may trigger.
+ *
+ * @param clazz the class to validate
+ * @throws SecurityException if the class is not trusted
+ */
+ public static void validate(Class> clazz) {
+ while (clazz.isArray()) {
+ clazz = clazz.getComponentType();
+ }
+ if (clazz.isPrimitive()) {
+ return;
+ }
+ if (!globalInstance.isTrusted(clazz)) {
+ globalInstance.forbiddenClass(clazz.getName());
+ }
+ }
+
+ /**
+ * Sets the global {@link ClassSecurityPredicate} that is used by
+ * {@link ClassUtils} to validate the trusted classes.
+ *
+ * @param validator the validator to use
+ */
+ public static void setGlobal(ClassSecurityPredicate validator) {
+ globalInstance = Objects.requireNonNull(validator);
+ }
+
+ /**
+ * Returns the global {@link ClassSecurityPredicate} that is used by
+ * {@link ClassUtils} to validate the trusted classes.
+ *
+ * @return the global validator
+ */
+ public static ClassSecurityPredicate getGlobal() {
+ return globalInstance;
+ }
+
+ private ClassSecurityValidator() {
+ }
+
+ /**
+ * A predicate that checks if a class is trusted to be included in Avro schemas.
+ */
+ public interface ClassSecurityPredicate {
+ /**
+ * Checks if the class is trusted to be included in Avro schemas.
+ *
+ * @param clazz the class to check
+ * @return true if the class is trusted, false otherwise
+ */
+ boolean isTrusted(Class> clazz);
+
+ /**
+ * Throws a {@link SecurityException} with a message indicating that the class
+ * is not trusted to be included in Avro schemas.
+ *
+ * @param className the name of the class that is not trusted
+ */
+ default void forbiddenClass(String className) {
+ throw new SecurityException("Forbidden " + className + "! This class is not trusted to be included in Avro "
+ + "schemas. You may either use the system properties org.apache.avro.SERIALIZABLE_CLASSES and "
+ + "org.apache.avro.SERIALIZABLE_PACKAGES to set the comma separated list of the classes or packages you trust, "
+ + "or you can set them via the API (see org.apache.avro.util.ClassSecurityValidator).");
+ }
+ }
+
+ /**
+ * A couple of trusted classes that are safe to be loaded, instantiated with any
+ * constructors or alter any internals via reflection.
+ */
+ public static final ClassSecurityPredicate DEFAULT_TRUSTED_CLASSES = builder().add("java.lang.Boolean")
+ .add("java.lang.Byte").add("java.lang.Character").add("java.lang.CharSequence").add("java.lang.Double")
+ .add("java.lang.Enum").add("java.lang.Float").add("java.lang.Integer").add("java.lang.Long")
+ .add("java.lang.Number").add("java.lang.Object").add("java.lang.Short").add("java.lang.String")
+ .add("java.lang.Void").add("java.math.BigDecimal").add("java.math.BigInteger").build();
+
+ /**
+ * The predicate that uses the system properties
+ * {@value SystemPropertiesPredicate#SYSPROP_SERIALIZABLE_CLASSES} and
+ * {@value SystemPropertiesPredicate#SYSPROP_SERIALIZABLE_PACKAGES}.
+ */
+ public static final ClassSecurityPredicate SYSTEM_PROPERTIES = new SystemPropertiesPredicate();
+
+ /**
+ * The default predicate that uses both the system properties and the hard-coded
+ * trusted classes.
+ *
+ * @see #DEFAULT_TRUSTED_CLASSES
+ * @see #SYSTEM_PROPERTIES
+ */
+ public static final ClassSecurityPredicate DEFAULT = composite(DEFAULT_TRUSTED_CLASSES, SYSTEM_PROPERTIES);
+
+ private static ClassSecurityPredicate globalInstance = DEFAULT;
+
+ /**
+ * Creates a builder for a {@link ClassSecurityValidator} that validates the
+ * trusted classes by whitelisting their names. Note: no parent validator is
+ * used.
+ *
+ * @return a new {@link ClassSecurityValidator} builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a composite {@link ClassSecurityValidator} that delegates to the
+ * given validators.
+ *
+ * @param validators the validators to delegate to
+ * @return a new {@link ClassSecurityValidator} that delegates to the given
+ * validators
+ */
+ public static ClassSecurityPredicate composite(ClassSecurityPredicate... validators) {
+ return clazz -> Arrays.stream(validators).anyMatch(v -> v.isTrusted(clazz));
+ }
+
+ public static class Builder {
+ private final Set allowedClassNames = new HashSet<>();
+
+ private Builder() {
+ }
+
+ public Builder add(String className) {
+ allowedClassNames.add(className);
+ return this;
+ }
+
+ public Builder add(Class> clazz) {
+ return add(clazz.getName());
+ }
+
+ public ClassSecurityPredicate build() {
+ return clazz -> allowedClassNames.contains(clazz.getName());
+ }
+ }
+
+ public static class SystemPropertiesPredicate implements ClassSecurityPredicate {
+
+ /**
+ * The set of trusted classes specified by the system property
+ * {@value #SYSPROP_SERIALIZABLE_CLASSES}. Empty by default.
+ */
+ public static final Set SERIALIZABLE_CLASSES;
+
+ /**
+ * The set of trusted packages specified by the system property
+ * {@value #SYSPROP_SERIALIZABLE_PACKAGES}. Empty by default.
+ */
+ public static final NavigableSet SERIALIZABLE_PACKAGES;
+
+ private static final boolean TRUST_ALL_PACKAGES;
+
+ private static final String SYSPROP_SERIALIZABLE_CLASSES = "org.apache.avro.SERIALIZABLE_CLASSES";
+
+ private static final String SYSPROP_SERIALIZABLE_PACKAGES = "org.apache.avro.SERIALIZABLE_PACKAGES";
+
+ static {
+ // add the hard-coded trusted classes as well
+ SERIALIZABLE_CLASSES = Collections.unmodifiableSet(
+ streamPropertyEntries(System.getProperty(SYSPROP_SERIALIZABLE_CLASSES)).collect(Collectors.toSet()));
+
+ // no default serializable packages are hard-coded
+ NavigableSet packages = streamPropertyEntries(System.getProperty(SYSPROP_SERIALIZABLE_PACKAGES))
+ // Add a '.' suffix to ensure we'll be matching package names instead of
+ // arbitrary prefixes, except for the wildcard "*", which allows all
+ // packages (this is only safe in fully controlled environments!).
+ .map(entry -> "*".equals(entry) ? entry : entry + ".").collect(TreeSet::new, TreeSet::add, TreeSet::addAll);
+ TRUST_ALL_PACKAGES = packages.remove("*");
+
+ SERIALIZABLE_PACKAGES = Collections.unmodifiableNavigableSet(packages);
+ }
+
+ /**
+ * Parse a comma separated list into non-empty entries. Leading and trailing
+ * whitespace is stripped.
+ *
+ * @param commaSeparatedEntries the comma separated list of entries
+ * @return a stream of the entries
+ */
+ private static Stream streamPropertyEntries(String commaSeparatedEntries) {
+ if (commaSeparatedEntries == null) {
+ return Stream.empty();
+ }
+ return Stream.of(commaSeparatedEntries.split(",")).map(s -> s.replaceAll("^\\s+|\\s+$", ""))
+ .filter(s -> !s.isEmpty());
+ }
+
+ private SystemPropertiesPredicate() {
+ }
+
+ @Override
+ public boolean isTrusted(Class> clazz) {
+ if (TRUST_ALL_PACKAGES) {
+ return true;
+ }
+
+ String className = clazz.getName();
+
+ if (SERIALIZABLE_CLASSES.contains(className)) {
+ return true;
+ }
+
+ String lower = SERIALIZABLE_PACKAGES.lower(className);
+ return lower != null && className.startsWith(lower);
+ }
+ }
+}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/util/ClassUtils.java b/lang/java/avro/src/main/java/org/apache/avro/util/ClassUtils.java
index dad59a551d6..c21f276d6d0 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/util/ClassUtils.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/util/ClassUtils.java
@@ -52,7 +52,7 @@ public static Class> forName(Class> contextClass, String className) throws C
c = forName(className, Thread.currentThread().getContextClassLoader());
}
if (c == null) {
- throw new ClassNotFoundException("Failed to load class" + className);
+ throw new ClassNotFoundException("Failed to load class " + className);
}
return c;
}
@@ -75,14 +75,14 @@ public static Class> forName(ClassLoader classLoader, String className) throws
c = forName(className, Thread.currentThread().getContextClassLoader());
}
if (c == null) {
- throw new ClassNotFoundException("Failed to load class" + className);
+ throw new ClassNotFoundException("Failed to load class " + className);
}
return c;
}
/**
* Loads a {@link Class} from the specified {@link ClassLoader} without throwing
- * {@link ClassNotFoundException}.
+ * {@link ClassNotFoundException}. The class is loaded without initialization.
*
* @param className
* @param classLoader
@@ -92,7 +92,11 @@ private static Class> forName(String className, ClassLoader classLoader) {
Class> c = null;
if (classLoader != null && className != null) {
try {
- c = Class.forName(className, true, classLoader);
+ // Load the class without initializing it so we can distinguish between
+ // ClassNotFoundException and SecurityException (that may be thrown by the
+ // validator).
+ c = Class.forName(className, false, classLoader);
+ ClassSecurityValidator.validate(c);
} catch (ClassNotFoundException e) {
// Ignore and return null
}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectDatumReader.java b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectDatumReader.java
index 52b40b87b36..ecd2cecb677 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectDatumReader.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectDatumReader.java
@@ -19,6 +19,7 @@
package org.apache.avro.reflect;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -35,6 +36,8 @@
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;
+import org.apache.avro.util.ClassSecurityValidator;
+import org.apache.avro.util.ClassSecurityValidator.ClassSecurityPredicate;
import org.junit.jupiter.api.Test;
public class TestReflectDatumReader {
@@ -49,6 +52,21 @@ private static byte[] serializeWithReflectDatumWriter(T toSerialize, Class new ReflectDatumReader<>(PojoWithArray.class));
+ } finally {
+ ClassSecurityValidator.setGlobal(originalValidator);
+ }
+ }
+
@Test
void read_PojoWithList() throws IOException {
PojoWithList pojoWithList = new PojoWithList();
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java
index e64b3f4c220..70f3e7ac90a 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificRecordWithUnion.java
@@ -30,6 +30,8 @@
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.Decoder;
+import org.apache.avro.util.ClassSecurityValidator;
+import org.apache.avro.util.ClassSecurityValidator.ClassSecurityPredicate;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
@@ -38,8 +40,30 @@
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
public class TestSpecificRecordWithUnion {
+ /**
+ * Test that the deserialization of a class that is not trusted throws a
+ * SecurityException.
+ */
+ @Test
+ void testNotSerializableClasses() throws IOException {
+ final TestUnionRecord record = TestUnionRecord.newBuilder().setAmount(BigDecimal.ZERO).build();
+ final Schema schema = SchemaBuilder.unionOf().nullType().and().type(record.getSchema()).endUnion();
+
+ byte[] recordBytes = serializeRecord(
+ "{ \"org.apache.avro.specific.TestUnionRecord\": { \"amount\": { \"bytes\": \"\\u0000\" } } }", schema);
+
+ ClassSecurityPredicate originalValidator = ClassSecurityValidator.getGlobal();
+ try {
+ ClassSecurityValidator.setGlobal(ClassSecurityValidator.builder().build());
+ assertThrows(SecurityException.class, () -> deserializeRecord(schema, recordBytes));
+ } finally {
+ ClassSecurityValidator.setGlobal(originalValidator);
+ }
+ }
+
@Test
void unionLogicalDecimalConversion() throws IOException {
final TestUnionRecord record = TestUnionRecord.newBuilder().setAmount(BigDecimal.ZERO).build();
@@ -48,11 +72,14 @@ void unionLogicalDecimalConversion() throws IOException {
byte[] recordBytes = serializeRecord(
"{ \"org.apache.avro.specific.TestUnionRecord\": { \"amount\": { \"bytes\": \"\\u0000\" } } }", schema);
+ assertEquals(record, deserializeRecord(schema, recordBytes));
+ }
+
+ private static SpecificRecord deserializeRecord(Schema schema, byte[] recordBytes) throws IOException {
SpecificDatumReader specificDatumReader = new SpecificDatumReader<>(schema);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(recordBytes);
Decoder decoder = DecoderFactory.get().binaryDecoder(byteArrayInputStream, null);
- final SpecificRecord deserialized = specificDatumReader.read(null, decoder);
- assertEquals(record, deserialized);
+ return specificDatumReader.read(null, decoder);
}
public static byte[] serializeRecord(String value, Schema schema) throws IOException {
diff --git a/lang/java/avro/src/test/java/org/apache/avro/util/TestClassSecurityValidator.java b/lang/java/avro/src/test/java/org/apache/avro/util/TestClassSecurityValidator.java
new file mode 100644
index 00000000000..4dc1a55c54f
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/util/TestClassSecurityValidator.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro.util;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.math.BigInteger;
+import org.apache.avro.util.ClassSecurityValidator.ClassSecurityPredicate;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestClassSecurityValidator {
+
+ // To test inner classes
+ private static class TestInnerClass {
+ }
+
+ private ClassSecurityPredicate originalValidator;
+
+ @BeforeEach
+ public void saveOriginalValidator() {
+ originalValidator = ClassSecurityValidator.getGlobal();
+ }
+
+ @AfterEach
+ public void restoreOriginalValidator() {
+ ClassSecurityValidator.setGlobal(originalValidator);
+ }
+
+ @Test
+ void testDefault() {
+ // Test a couple of default trusted classes via ClassUtils
+ assertDoesNotThrow(() -> ClassUtils.forName(boolean[][][][][][].class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName("java.lang.String"));
+ assertDoesNotThrow(() -> ClassUtils.forName(java.math.BigDecimal[][][][].class.getName()));
+
+ // The package "org.apache.avro" is allowed by default for the test environment
+ assertDoesNotThrow(() -> ClassUtils.forName("org.apache.avro.util.TestClassSecurityValidator$TestInnerClass"));
+
+ // Test a couple of default untrusted classes via ClassUtils
+ assertThrows(SecurityException.class, () -> ClassUtils.forName("java.net.InetAddress"));
+ assertThrows(SecurityException.class, () -> ClassUtils.forName("java.io.FileInputStream"));
+ }
+
+ @Test
+ void testBuilder() {
+ ClassSecurityValidator.setGlobal(ClassSecurityValidator.builder().add(TestClassSecurityValidator.class).build());
+
+ assertDoesNotThrow(() -> ClassUtils.forName("org.apache.avro.util.TestClassSecurityValidator"));
+ assertThrows(SecurityException.class,
+ () -> ClassUtils.forName("org.apache.avro.util.TestClassSecurityValidator$TestInnerClass"));
+
+ // Test that arrays and primitives also work
+ assertDoesNotThrow(() -> ClassUtils.forName(short[][][][][].class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName(TestClassSecurityValidator[][][][].class.getName()));
+ assertThrows(SecurityException.class, () -> ClassUtils.forName(TestInnerClass[][].class.getName()));
+ }
+
+ @Test
+ void testOwnImplementation() {
+ ClassSecurityValidator.setGlobal(new ClassSecurityPredicate() {
+ @Override
+ public boolean isTrusted(Class> clazz) {
+ return clazz.getSimpleName().contains("Inner");
+ }
+
+ @Override
+ public void forbiddenClass(String className) {
+ throw new SecurityException("Not inner");
+ }
+ });
+ assertDoesNotThrow(() -> ClassUtils.forName(TestInnerClass.class.getName()));
+ Exception e = assertThrows(SecurityException.class,
+ () -> ClassUtils.forName(TestClassSecurityValidator.class.getName()));
+ assertEquals("Not inner", e.getMessage());
+
+ // Test that arrays and primitives also work
+ assertDoesNotThrow(() -> ClassUtils.forName(char[][][][].class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName(TestInnerClass[][][][].class.getName()));
+ e = assertThrows(SecurityException.class, () -> ClassUtils.forName(TestClassSecurityValidator[][].class.getName()));
+ assertEquals("Not inner", e.getMessage());
+ }
+
+ @Test
+ void testBuildComplexPredicate() {
+ ClassSecurityValidator.setGlobal(ClassSecurityValidator.composite(
+ ClassSecurityValidator.builder().add(TestInnerClass.class).add(TestClassSecurityValidator.class).build(),
+ ClassSecurityValidator.DEFAULT, c -> c.getPackageName().equals("java.lang")));
+
+ // Test that the defaults work since we included them
+ testDefault();
+
+ assertDoesNotThrow(() -> ClassUtils.forName(TestInnerClass.class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName(TestClassSecurityValidator.class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName(StringBuilder.class.getName()));
+ assertDoesNotThrow(() -> ClassUtils.forName("java.lang.StringBuffer"));
+ assertDoesNotThrow(() -> ClassUtils.forName(BigInteger.class.getName()));
+ }
+}
diff --git a/lang/java/interop-data-test/src/it/check/pom.xml b/lang/java/interop-data-test/src/it/check/pom.xml
index 34159171689..64eeaf4c850 100644
--- a/lang/java/interop-data-test/src/it/check/pom.xml
+++ b/lang/java/interop-data-test/src/it/check/pom.xml
@@ -99,6 +99,23 @@
false
${interop.datadir}
+
+
+
+ java.net.URI,
+ java.net.URL,
+ java.io.File,
+ java.util.HashMap,
+ java.util.List,
+ java.util.Collection,
+ java.util.Map,
+ java.util.Set,
+ java.util.concurrent.ConcurrentHashMap,
+ java.util.LinkedHashMap,
+ java.util.TreeMap
+
+ org.apache.avro
+
diff --git a/lang/java/ipc/pom.xml b/lang/java/ipc/pom.xml
index 5d18931d8e0..6f5fb359df9 100644
--- a/lang/java/ipc/pom.xml
+++ b/lang/java/ipc/pom.xml
@@ -61,10 +61,7 @@
1
false
- none
-
- java.math.BigDecimal,java.math.BigInteger
-
+ none
diff --git a/lang/java/mapred/src/test/java/org/apache/avro/mapred/tether/TestWordCountTether.java b/lang/java/mapred/src/test/java/org/apache/avro/mapred/tether/TestWordCountTether.java
index e538895f758..5f621d28a84 100644
--- a/lang/java/mapred/src/test/java/org/apache/avro/mapred/tether/TestWordCountTether.java
+++ b/lang/java/mapred/src/test/java/org/apache/avro/mapred/tether/TestWordCountTether.java
@@ -77,6 +77,7 @@ private void _runjob(String proto) throws Exception {
List execargs = new ArrayList<>();
execargs.add("-classpath");
execargs.add(System.getProperty("java.class.path"));
+ execargs.add("-Dorg.apache.avro.SERIALIZABLE_PACKAGES=org.apache.avro");
execargs.add("org.apache.avro.mapred.tether.WordCountTask");
FileInputFormat.addInputPaths(job, inputPath.toString());
diff --git a/lang/java/pom.xml b/lang/java/pom.xml
index a80c177121d..82f482963cc 100644
--- a/lang/java/pom.xml
+++ b/lang/java/pom.xml
@@ -194,6 +194,30 @@
true
false
-Xmx1000m
+
+
+
+
+ java.net.URI,
+ java.net.URL,
+ java.io.File,
+ java.util.HashMap,
+ java.util.List,
+ java.util.Collection,
+ java.util.Map,
+ java.util.Set,
+ java.util.concurrent.ConcurrentHashMap,
+ java.util.LinkedHashMap,
+ java.util.TreeMap,
+ example.avro.Bar,
+ com.google.protobuf.Timestamp
+
+
+ org.apache.avro,
+ test
+
+
+
diff --git a/lang/java/tools/src/test/java/org/apache/avro/tool/TestTetherTool.java b/lang/java/tools/src/test/java/org/apache/avro/tool/TestTetherTool.java
index c4e5639e2d1..d5fc1f35819 100644
--- a/lang/java/tools/src/test/java/org/apache/avro/tool/TestTetherTool.java
+++ b/lang/java/tools/src/test/java/org/apache/avro/tool/TestTetherTool.java
@@ -76,6 +76,7 @@ void test() throws Exception {
// create a string of the arguments
String execargs = "-classpath " + System.getProperty("java.class.path");
+ execargs += " -Dorg.apache.avro.SERIALIZABLE_PACKAGES=org.apache.avro";
execargs += " org.apache.avro.mapred.tether.WordCountTask";
// Create a list of the arguments to pass to the tull run method