Skip to content
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

[SCB-965] Fix jackson DoS problem #961

Merged
merged 2 commits into from
Oct 23, 2018
Merged
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
@@ -0,0 +1,151 @@
/*
* 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
*
* http://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 com.fasterxml.jackson.core.base;

import org.apache.servicecomb.foundation.common.utils.JvmUtils;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper;
import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.netflix.config.DynamicPropertyFactory;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
* will be deleted after jackson fix the DoS problem:
* https://github.com/FasterXML/jackson-databind/issues/2157
*/
public class DoSFix {
private static final String SUFFIX = "Fixed";

private static boolean enabled = DynamicPropertyFactory.getInstance()
.getBooleanProperty("servicecomb.jackson.fix.DoS.enabled", true).get();

private static boolean fixed;

private static Class<?> mappingJsonFactoryClass;

public static synchronized void init() {
if (fixed || !enabled) {
return;
}

fix();
}

public static JsonFactory createJsonFactory() {
try {
return (JsonFactory) mappingJsonFactoryClass.newInstance();
} catch (Throwable e) {
throw new IllegalStateException("Failed to create JsonFactory.", e);
}
}

private static void fix() {
try {
ClassLoader classLoader = JvmUtils.correctClassLoader(DoSFix.class.getClassLoader());
ClassPool pool = new ClassPool(ClassPool.getDefault());
pool.appendClassPath(new LoaderClassPath(classLoader));

fixParserBase(classLoader, pool);
fixReaderParser(classLoader, pool);
fixStreamParser(classLoader, pool);
fixByteSourceJsonBootstrapper(classLoader, pool);

CtClass ctJsonFactoryFixedClass = fixJsonFactory(classLoader, pool);
fixMappingJsonFactoryClass(classLoader, pool, ctJsonFactoryFixedClass);

fixed = true;
} catch (Throwable e) {
throw new IllegalStateException(
"Failed to fix jackson DoS bug.",
e);
}
}

private static void fixMappingJsonFactoryClass(ClassLoader classLoader, ClassPool pool,
CtClass ctJsonFactoryFixedClass) throws NotFoundException, CannotCompileException {
CtClass ctMappingJsonFactoryClass = pool
.getAndRename(MappingJsonFactory.class.getName(), MappingJsonFactory.class.getName() + SUFFIX);
ctMappingJsonFactoryClass.setSuperclass(ctJsonFactoryFixedClass);
mappingJsonFactoryClass = ctMappingJsonFactoryClass.toClass(classLoader, null);
}

private static CtClass fixJsonFactory(ClassLoader classLoader, ClassPool pool)
throws NotFoundException, CannotCompileException {
CtClass ctJsonFactoryClass = pool.getCtClass(JsonFactory.class.getName());
CtClass ctJsonFactoryFixedClass = pool.makeClass(JsonFactory.class.getName() + SUFFIX);
ctJsonFactoryFixedClass.setSuperclass(ctJsonFactoryClass);
for (CtMethod ctMethod : ctJsonFactoryClass.getDeclaredMethods()) {
if (ctMethod.getName().equals("_createParser")) {
ctJsonFactoryFixedClass.addMethod(new CtMethod(ctMethod, ctJsonFactoryFixedClass, null));
}
}
ctJsonFactoryFixedClass
.replaceClassName(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
ctJsonFactoryFixedClass
.replaceClassName(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
ctJsonFactoryFixedClass.replaceClassName(ByteSourceJsonBootstrapper.class.getName(),
ByteSourceJsonBootstrapper.class.getName() + SUFFIX);
ctJsonFactoryFixedClass.toClass(classLoader, null);

return ctJsonFactoryFixedClass;
}

private static void fixByteSourceJsonBootstrapper(ClassLoader classLoader, ClassPool pool)
throws NotFoundException, CannotCompileException {
CtClass ctByteSourceJsonBootstrapper = pool
.getAndRename(ByteSourceJsonBootstrapper.class.getName(), ByteSourceJsonBootstrapper.class.getName() + SUFFIX);
ctByteSourceJsonBootstrapper
.replaceClassName(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
ctByteSourceJsonBootstrapper
.replaceClassName(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
ctByteSourceJsonBootstrapper.toClass(classLoader, null);
}

private static void fixStreamParser(ClassLoader classLoader, ClassPool pool)
throws NotFoundException, CannotCompileException {
CtClass ctStreamClass = pool
.getAndRename(UTF8StreamJsonParser.class.getName(), UTF8StreamJsonParser.class.getName() + SUFFIX);
ctStreamClass.replaceClassName(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
ctStreamClass.toClass(classLoader, null);
}

private static void fixReaderParser(ClassLoader classLoader, ClassPool pool)
throws NotFoundException, CannotCompileException {
CtClass ctReaderClass = pool
.getAndRename(ReaderBasedJsonParser.class.getName(), ReaderBasedJsonParser.class.getName() + SUFFIX);
ctReaderClass.replaceClassName(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
ctReaderClass.toClass(classLoader, null);
}

private static void fixParserBase(ClassLoader classLoader, ClassPool pool)
throws NotFoundException, CannotCompileException {
CtMethod ctMethodFixed = pool.get(DoSParserFixed.class.getName()).getDeclaredMethod("_parseSlowInt");
CtClass baseClass = pool.getAndRename(ParserBase.class.getName(), ParserBase.class.getName() + SUFFIX);
baseClass.removeMethod(baseClass.getDeclaredMethod("_parseSlowInt"));
baseClass.addMethod(new CtMethod(ctMethodFixed, baseClass, null));
baseClass.toClass(classLoader, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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
*
* http://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 com.fasterxml.jackson.core.base;

import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;

import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.json.ReaderBasedJsonParser;
import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;

/**
* will not be use directly
* just get _parseSlowInt/_parseSlowFloat bytecode and replace to ParserBase
*/
public abstract class DoSParserFixed extends ReaderBasedJsonParser {
public DoSParserFixed(IOContext ctxt, int features, Reader r,
ObjectCodec codec, CharsToNameCanonicalizer st,
char[] inputBuffer, int start, int end, boolean bufferRecyclable) {
super(ctxt, features, r, codec, st, inputBuffer, start, end, bufferRecyclable);
}

private void _parseSlowInt(int expType) throws IOException {
String numStr = _textBuffer.contentsAsString();
try {
int len = _intLength;
char[] buf = _textBuffer.getTextBuffer();
int offset = _textBuffer.getTextOffset();
if (_numberNegative) {
++offset;
}
// Some long cases still...
if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) {
// Probably faster to construct a String, call parse, than to use BigInteger
_numberLong = Long.parseLong(numStr);
_numTypesValid = NR_LONG;
} else {
// nope, need the heavy guns... (rare case)

// *** fix DoS attack begin ***
if (NR_DOUBLE == expType || NR_FLOAT == expType) {
_numberDouble = Double.parseDouble(numStr);
_numTypesValid = NR_DOUBLE;
return;
}
if (NR_BIGINT != expType) {
throw new NumberFormatException("invalid numeric value '" + numStr + "'");
}
// *** fix DoS attack end ***

_numberBigInt = new BigInteger(numStr);
_numTypesValid = NR_BIGINT;
}
} catch (NumberFormatException nex) {
// Can this ever occur? Due to overflow, maybe?
_wrapError("Malformed numeric value '" + numStr + "'", nex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@

package org.apache.servicecomb.common.rest.codec;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;

public abstract class AbstractRestObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 189026839992490564L;

public AbstractRestObjectMapper(JsonFactory jsonFactory) {
super(jsonFactory);
}

abstract public String convertToString(Object value) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.base.DoSFix;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
Expand All @@ -34,6 +35,10 @@
import io.vertx.core.json.JsonObject;

public class RestObjectMapper extends AbstractRestObjectMapper {
static {
DoSFix.init();
}

private static class JsonObjectSerializer extends JsonSerializer<JsonObject> {
@Override
public void serialize(JsonObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
Expand All @@ -47,6 +52,8 @@ public void serialize(JsonObject value, JsonGenerator jgen, SerializerProvider p

@SuppressWarnings("deprecation")
public RestObjectMapper() {
super(DoSFix.createJsonFactory());

// swagger中要求date使用ISO8601格式传递,这里与之做了功能绑定,这在cse中是没有问题的
setDateFormat(new com.fasterxml.jackson.databind.util.ISO8601DateFormat() {
private static final long serialVersionUID = 7798938088541203312L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class RestObjectMapperFactory {
private static AbstractRestObjectMapper defaultMapper = new RestObjectMapper();

private static AbstractRestObjectMapper consumerWriterMapper = new RestObjectMapper();
private static AbstractRestObjectMapper consumerWriterMapper = defaultMapper;

public static AbstractRestObjectMapper getConsumerWriterMapper() {
return consumerWriterMapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ws.rs.core.MediaType;

import org.apache.servicecomb.foundation.common.RegisterManager;
Expand Down Expand Up @@ -47,8 +48,9 @@ private ProduceProcessorManager() {
super(NAME);
Set<String> set = new HashSet<>();
produceProcessor.forEach(processor -> {
if (set.add(processor.getName()))
if (set.add(processor.getName())) {
register(processor.getName(), processor);
}
});
}
}
Loading