Skip to content

Commit b406060

Browse files
authored
YARN-8972. [Router] Add support to prevent DoS attack over ApplicationSubmissionContext size. (#5382)
1 parent 487368c commit b406060

File tree

8 files changed

+711
-6
lines changed

8 files changed

+711
-6
lines changed

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4249,6 +4249,13 @@ public static boolean isAclEnabled(Configuration conf) {
42494249
"org.apache.hadoop.yarn.server.router.webapp."
42504250
+ "DefaultRequestInterceptorREST";
42514251

4252+
/**
4253+
* ApplicationSubmissionContextInterceptor configurations.
4254+
**/
4255+
public static final String ROUTER_ASC_INTERCEPTOR_MAX_SIZE =
4256+
ROUTER_PREFIX + "asc-interceptor-max-size";
4257+
public static final String DEFAULT_ROUTER_ASC_INTERCEPTOR_MAX_SIZE = "1MB";
4258+
42524259
/**
42534260
* The interceptor class used in FederationInterceptorREST should return
42544261
* partial AppReports.

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5117,6 +5117,16 @@
51175117
</description>
51185118
</property>
51195119

5120+
<property>
5121+
<name>yarn.router.asc-interceptor-max-size</name>
5122+
<value>1MB</value>
5123+
<description>
5124+
We define the size limit of ApplicationSubmissionContext.
5125+
If the size of the ApplicationSubmissionContext is larger than this value,
5126+
We will throw an exception. the default value is 1MB.
5127+
</description>
5128+
</property>
5129+
51205130
<property>
51215131
<description>
51225132
The number of threads to use for the Router scheduled executor service.

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/RouterServerUtil.java

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,44 @@
1818

1919
package org.apache.hadoop.yarn.server.router;
2020

21+
import org.apache.commons.collections.CollectionUtils;
2122
import org.apache.commons.lang3.math.NumberUtils;
2223
import org.apache.hadoop.classification.InterfaceAudience.Private;
2324
import org.apache.hadoop.classification.InterfaceAudience.Public;
2425
import org.apache.hadoop.classification.InterfaceStability.Unstable;
2526
import org.apache.hadoop.conf.Configuration;
27+
import org.apache.hadoop.conf.StorageUnit;
2628
import org.apache.hadoop.security.UserGroupInformation;
2729
import org.apache.hadoop.security.token.Token;
30+
import org.apache.hadoop.thirdparty.protobuf.GeneratedMessageV3;
2831
import org.apache.hadoop.util.ReflectionUtils;
2932
import org.apache.hadoop.util.StringUtils;
3033
import org.apache.hadoop.yarn.api.records.ReservationRequest;
3134
import org.apache.hadoop.yarn.api.records.Priority;
3235
import org.apache.hadoop.yarn.api.records.ReservationRequestInterpreter;
3336
import org.apache.hadoop.yarn.api.records.Resource;
3437
import org.apache.hadoop.yarn.api.records.ReservationRequests;
38+
import org.apache.hadoop.yarn.api.records.impl.pb.ApplicationSubmissionContextPBImpl;
39+
import org.apache.hadoop.yarn.api.records.impl.pb.ContainerLaunchContextPBImpl;
40+
import org.apache.hadoop.yarn.conf.YarnConfiguration;
41+
import org.apache.hadoop.yarn.proto.YarnProtos.StringStringMapProto;
42+
import org.apache.hadoop.yarn.proto.YarnProtos.StringBytesMapProto;
43+
import org.apache.hadoop.yarn.proto.YarnProtos.ApplicationACLMapProto;
44+
import org.apache.hadoop.yarn.proto.YarnProtos.StringLocalResourceMapProto;
3545
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDefinitionInfo;
3646
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationRequestsInfo;
3747
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationRequestInfo;
3848
import org.apache.hadoop.yarn.api.records.ReservationDefinition;
49+
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
3950
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
4051
import org.apache.hadoop.yarn.exceptions.YarnException;
4152
import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
4253
import org.apache.hadoop.yarn.security.client.RMDelegationTokenIdentifier;
4354
import org.slf4j.Logger;
4455
import org.slf4j.LoggerFactory;
4556

57+
import java.io.ByteArrayOutputStream;
58+
import java.io.ObjectOutputStream;
4659
import java.lang.reflect.InvocationTargetException;
4760
import java.lang.reflect.Method;
4861
import java.util.ArrayList;
@@ -624,4 +637,118 @@ public static ReservationDefinition convertReservationDefinition(
624637

625638
return definition;
626639
}
640+
641+
/**
642+
* Checks if the ApplicationSubmissionContext submitted with the application
643+
* is valid.
644+
*
645+
* Current checks:
646+
* - if its size is within limits.
647+
*
648+
* @param appContext the app context to check.
649+
* @throws IOException if an IO error occurred.
650+
* @throws YarnException yarn exception.
651+
*/
652+
@Public
653+
@Unstable
654+
public static void checkAppSubmissionContext(ApplicationSubmissionContextPBImpl appContext,
655+
Configuration conf) throws IOException, YarnException {
656+
// Prevents DoS over the ApplicationClientProtocol by checking the context
657+
// the application was submitted with for any excessively large fields.
658+
double bytesOfMaxAscSize = conf.getStorageSize(
659+
YarnConfiguration.ROUTER_ASC_INTERCEPTOR_MAX_SIZE,
660+
YarnConfiguration.DEFAULT_ROUTER_ASC_INTERCEPTOR_MAX_SIZE, StorageUnit.BYTES);
661+
if (appContext != null) {
662+
int bytesOfSerializedSize = appContext.getProto().getSerializedSize();
663+
if (bytesOfSerializedSize >= bytesOfMaxAscSize) {
664+
logContainerLaunchContext(appContext);
665+
String applicationId = appContext.getApplicationId().toString();
666+
String limit = StringUtils.byteDesc((long) bytesOfMaxAscSize);
667+
String appContentSize = StringUtils.byteDesc(bytesOfSerializedSize);
668+
String errMsg = String.format(
669+
"The size of the ApplicationSubmissionContext of the application %s is " +
670+
"above the limit %s, size = %s.", applicationId, limit, appContentSize);
671+
LOG.error(errMsg);
672+
throw new YarnException(errMsg);
673+
}
674+
}
675+
}
676+
677+
/**
678+
* Private helper for checkAppSubmissionContext that logs the fields in the
679+
* context for debugging.
680+
*
681+
* @param appContext the app context.
682+
* @throws IOException if an IO error occurred.
683+
*/
684+
@Private
685+
@Unstable
686+
private static void logContainerLaunchContext(ApplicationSubmissionContextPBImpl appContext)
687+
throws IOException {
688+
if (appContext == null || appContext.getAMContainerSpec() == null ||
689+
!(appContext.getAMContainerSpec() instanceof ContainerLaunchContextPBImpl)) {
690+
return;
691+
}
692+
693+
ContainerLaunchContext launchContext = appContext.getAMContainerSpec();
694+
ContainerLaunchContextPBImpl clc = (ContainerLaunchContextPBImpl) launchContext;
695+
LOG.warn("ContainerLaunchContext size: {}.", clc.getProto().getSerializedSize());
696+
697+
// ContainerLaunchContext contains:
698+
// 1) Map<String, LocalResource> localResources,
699+
List<StringLocalResourceMapProto> lrs = clc.getProto().getLocalResourcesList();
700+
logContainerLaunchContext("LocalResource size: {}. Length: {}.", lrs);
701+
702+
// 2) Map<String, String> environment, List<String> commands,
703+
List<StringStringMapProto> envs = clc.getProto().getEnvironmentList();
704+
logContainerLaunchContext("Environment size: {}. Length: {}.", envs);
705+
706+
List<String> cmds = clc.getCommands();
707+
if (CollectionUtils.isNotEmpty(cmds)) {
708+
LOG.warn("Commands size: {}. Length: {}.", cmds.size(), serialize(cmds).length);
709+
}
710+
711+
// 3) Map<String, ByteBuffer> serviceData,
712+
List<StringBytesMapProto> serviceData = clc.getProto().getServiceDataList();
713+
logContainerLaunchContext("ServiceData size: {}. Length: {}.", serviceData);
714+
715+
// 4) Map<ApplicationAccessType, String> acls
716+
List<ApplicationACLMapProto> acls = clc.getProto().getApplicationACLsList();
717+
logContainerLaunchContext("ACLs size: {}. Length: {}.", acls);
718+
}
719+
720+
/**
721+
* Log ContainerLaunchContext Data SerializedSize.
722+
*
723+
* @param format format of logging.
724+
* @param lists data list.
725+
* @param <R> generic type R.
726+
*/
727+
private static <R extends GeneratedMessageV3> void logContainerLaunchContext(String format,
728+
List<R> lists) {
729+
if (CollectionUtils.isNotEmpty(lists)) {
730+
int sumLength = 0;
731+
for (R item : lists) {
732+
sumLength += item.getSerializedSize();
733+
}
734+
LOG.warn(format, lists.size(), sumLength);
735+
}
736+
}
737+
738+
/**
739+
* Serialize an object in ByteArray.
740+
*
741+
* @return obj ByteArray.
742+
* @throws IOException if an IO error occurred.
743+
*/
744+
@Private
745+
@Unstable
746+
private static byte[] serialize(Object obj) throws IOException {
747+
try (ByteArrayOutputStream b = new ByteArrayOutputStream()) {
748+
try (ObjectOutputStream o = new ObjectOutputStream(b)) {
749+
o.writeObject(obj);
750+
}
751+
return b.toByteArray();
752+
}
753+
}
627754
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.yarn.server.router.clientrm;
20+
21+
import java.io.IOException;
22+
23+
import org.apache.hadoop.yarn.api.protocolrecords.SubmitApplicationRequest;
24+
import org.apache.hadoop.yarn.api.protocolrecords.SubmitApplicationResponse;
25+
import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext;
26+
import org.apache.hadoop.yarn.api.records.impl.pb.ApplicationSubmissionContextPBImpl;
27+
import org.apache.hadoop.yarn.exceptions.YarnException;
28+
import org.apache.hadoop.yarn.server.router.RouterAuditLogger;
29+
import org.apache.hadoop.yarn.server.router.RouterMetrics;
30+
import org.apache.hadoop.yarn.server.router.RouterServerUtil;
31+
32+
import static org.apache.hadoop.yarn.server.router.RouterAuditLogger.AuditConstants.SUBMIT_NEW_APP;
33+
import static org.apache.hadoop.yarn.server.router.RouterAuditLogger.AuditConstants.TARGET_CLIENT_RM_SERVICE;
34+
import static org.apache.hadoop.yarn.server.router.RouterAuditLogger.AuditConstants.UNKNOWN;
35+
36+
/**
37+
* It prevents DoS attack over the ApplicationClientProtocol. Currently, it
38+
* checks the size of the ApplicationSubmissionContext. If it exceeds the limit
39+
* it can cause Zookeeper failures.
40+
*/
41+
public class ApplicationSubmissionContextInterceptor extends PassThroughClientRequestInterceptor {
42+
43+
@Override
44+
public SubmitApplicationResponse submitApplication(
45+
SubmitApplicationRequest request) throws YarnException, IOException {
46+
47+
if (request == null || request.getApplicationSubmissionContext() == null ||
48+
request.getApplicationSubmissionContext().getApplicationId() == null) {
49+
RouterMetrics.getMetrics().incrAppsFailedSubmitted();
50+
String errMsg =
51+
"Missing submitApplication request or applicationSubmissionContext information.";
52+
RouterAuditLogger.logFailure(user.getShortUserName(), SUBMIT_NEW_APP, UNKNOWN,
53+
TARGET_CLIENT_RM_SERVICE, errMsg);
54+
RouterServerUtil.logAndThrowException(errMsg, null);
55+
}
56+
57+
ApplicationSubmissionContext appContext = request.getApplicationSubmissionContext();
58+
ApplicationSubmissionContextPBImpl asc = (ApplicationSubmissionContextPBImpl) appContext;
59+
60+
// Check for excessively large fields, throw exception if found
61+
RouterServerUtil.checkAppSubmissionContext(asc, getConf());
62+
63+
// Check succeeded - app submit will be passed on to the next interceptor
64+
return getNextInterceptor().submitApplication(request);
65+
}
66+
}

0 commit comments

Comments
 (0)