1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import dalvik.system.VMRuntime; 18 import java.lang.invoke.MethodHandles; 19 import java.lang.invoke.MethodType; 20 import java.util.function.Consumer; 21 22 public class ChildClass { 23 enum PrimitiveType { 24 TInteger('I', Integer.TYPE, Integer.valueOf(0)), 25 TLong('J', Long.TYPE, Long.valueOf(0)), 26 TFloat('F', Float.TYPE, Float.valueOf(0)), 27 TDouble('D', Double.TYPE, Double.valueOf(0)), 28 TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)), 29 TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)), 30 TShort('S', Short.TYPE, Short.valueOf((short) 0)), 31 TCharacter('C', Character.TYPE, Character.valueOf('0')); 32 PrimitiveType(char shorty, Class klass, Object value)33 PrimitiveType(char shorty, Class klass, Object value) { 34 mShorty = shorty; 35 mClass = klass; 36 mDefaultValue = value; 37 } 38 39 public char mShorty; 40 public Class mClass; 41 public Object mDefaultValue; 42 } 43 44 enum Hiddenness { 45 Sdk(PrimitiveType.TShort), 46 Unsupported(PrimitiveType.TBoolean), 47 ConditionallyBlocked(PrimitiveType.TByte), 48 Blocklist(PrimitiveType.TCharacter), 49 BlocklistAndCorePlatformApi(PrimitiveType.TInteger); 50 Hiddenness(PrimitiveType type)51 Hiddenness(PrimitiveType type) { mAssociatedType = type; } 52 public PrimitiveType mAssociatedType; 53 } 54 55 enum Visibility { 56 Public(PrimitiveType.TInteger), 57 Package(PrimitiveType.TFloat), 58 Protected(PrimitiveType.TLong), 59 Private(PrimitiveType.TDouble); 60 Visibility(PrimitiveType type)61 Visibility(PrimitiveType type) { mAssociatedType = type; } 62 public PrimitiveType mAssociatedType; 63 } 64 65 enum Behaviour { 66 Granted, 67 Warning, 68 Denied, 69 } 70 71 // This needs to be kept in sync with DexDomain in Main. 72 enum DexDomain { 73 CorePlatform, 74 Platform, 75 Application 76 } 77 78 private static final boolean booleanValues[] = new boolean[] { false, true }; 79 runTest(String libFileName, int parentDomainOrdinal, int childDomainOrdinal, boolean everythingSdked)80 public static void runTest(String libFileName, int parentDomainOrdinal, 81 int childDomainOrdinal, boolean everythingSdked) throws Exception { 82 System.load(libFileName); 83 84 parentDomain = DexDomain.values()[parentDomainOrdinal]; 85 childDomain = DexDomain.values()[childDomainOrdinal]; 86 87 configMessage = "parentDomain=" + parentDomain.name() + ", childDomain=" + childDomain.name() 88 + ", everythingSdked=" + everythingSdked; 89 90 // Check expectations about loading into boot class path. 91 boolean isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null); 92 boolean expectedParentInBoot = (parentDomain != DexDomain.Application); 93 if (isParentInBoot != expectedParentInBoot) { 94 throw new RuntimeException("Expected ParentClass " + 95 (expectedParentInBoot ? "" : "not ") + "in boot class path"); 96 } 97 boolean isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null); 98 boolean expectedChildInBoot = (childDomain != DexDomain.Application); 99 if (isChildInBoot != expectedChildInBoot) { 100 throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") + 101 "in boot class path"); 102 } 103 ChildClass.everythingSdked = everythingSdked; 104 105 boolean isSameBoot = (isParentInBoot == isChildInBoot); 106 107 // For compat reasons, meta-reflection should still be usable by apps if hidden api check 108 // hardening is disabled (i.e. target SDK is Q or earlier). The only configuration where this 109 // workaround used to work is for ChildClass in the Application domain and ParentClass in the 110 // Platform domain, so only test that configuration with hidden api check hardening disabled. 111 boolean testHiddenApiCheckHardeningDisabled = 112 (childDomain == DexDomain.Application) && (parentDomain == DexDomain.Platform); 113 114 // Run meaningful combinations of access flags. 115 for (Hiddenness hiddenness : Hiddenness.values()) { 116 final Behaviour expected; 117 final boolean invokesMemberCallback; 118 // Warnings are now disabled whenever access is granted, even for 119 // greylisted APIs. This is the behaviour for release builds. 120 if (everythingSdked || hiddenness == Hiddenness.Sdk) { 121 expected = Behaviour.Granted; 122 invokesMemberCallback = false; 123 } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) { 124 expected = (hiddenness == Hiddenness.BlocklistAndCorePlatformApi) 125 ? Behaviour.Granted : Behaviour.Denied; 126 invokesMemberCallback = false; 127 } else if (isSameBoot) { 128 expected = Behaviour.Granted; 129 invokesMemberCallback = false; 130 } else if (hiddenness == Hiddenness.Blocklist || 131 hiddenness == Hiddenness.BlocklistAndCorePlatformApi) { 132 expected = Behaviour.Denied; 133 invokesMemberCallback = true; 134 } else { 135 expected = Behaviour.Warning; 136 invokesMemberCallback = true; 137 } 138 139 for (boolean isStatic : booleanValues) { 140 String suffix = (isStatic ? "Static" : "") + hiddenness.name(); 141 142 for (Visibility visibility : Visibility.values()) { 143 // Test reflection and JNI on methods and fields 144 for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) { 145 String baseName = visibility.name() + suffix; 146 checkField(klass, "field" + baseName, isStatic, visibility, expected, 147 invokesMemberCallback, testHiddenApiCheckHardeningDisabled); 148 checkMethod(klass, "method" + baseName, isStatic, visibility, expected, 149 invokesMemberCallback, testHiddenApiCheckHardeningDisabled); 150 } 151 152 // Check whether one can use a class constructor. 153 checkConstructor(ParentClass.class, visibility, hiddenness, expected, 154 testHiddenApiCheckHardeningDisabled); 155 156 // Check whether one can use an interface default method. 157 String name = "method" + visibility.name() + "Default" + hiddenness.name(); 158 checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected, 159 invokesMemberCallback, testHiddenApiCheckHardeningDisabled); 160 } 161 162 // Test whether static linking succeeds. 163 checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected); 164 checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected); 165 checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected); 166 checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected); 167 } 168 169 // Check whether Class.newInstance succeeds. 170 checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected); 171 } 172 } 173 174 static final class RecordingConsumer implements Consumer<String> { 175 public String recordedValue = null; 176 177 @Override accept(String value)178 public void accept(String value) { 179 recordedValue = value; 180 } 181 } 182 checkMemberCallback(Class<?> klass, String name, boolean isPublic, boolean isField, boolean expectedCallback)183 private static void checkMemberCallback(Class<?> klass, String name, 184 boolean isPublic, boolean isField, boolean expectedCallback) { 185 try { 186 RecordingConsumer consumer = new RecordingConsumer(); 187 VMRuntime.setNonSdkApiUsageConsumer(consumer); 188 try { 189 if (isPublic) { 190 if (isField) { 191 klass.getField(name); 192 } else { 193 klass.getMethod(name); 194 } 195 } else { 196 if (isField) { 197 klass.getDeclaredField(name); 198 } else { 199 klass.getDeclaredMethod(name); 200 } 201 } 202 } catch (NoSuchFieldException|NoSuchMethodException ignored) { 203 // We're not concerned whether an exception is thrown or not - we're 204 // only interested in whether the callback is invoked. 205 } 206 207 boolean actualCallback = consumer.recordedValue != null && 208 consumer.recordedValue.contains(name); 209 if (expectedCallback != actualCallback) { 210 if (expectedCallback) { 211 throw new RuntimeException("Expected callback for member: " + name); 212 } else { 213 throw new RuntimeException("Did not expect callback for member: " + name); 214 } 215 } 216 } finally { 217 VMRuntime.setNonSdkApiUsageConsumer(null); 218 } 219 } 220 checkField(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, boolean testHiddenApiCheckHardeningDisabled)221 private static void checkField(Class<?> klass, String name, boolean isStatic, 222 Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, 223 boolean testHiddenApiCheckHardeningDisabled) throws Exception { 224 225 boolean isPublic = (visibility == Visibility.Public); 226 boolean canDiscover = (behaviour != Behaviour.Denied); 227 228 if (klass.isInterface() && (!isStatic || !isPublic)) { 229 // Interfaces only have public static fields. 230 return; 231 } 232 233 // Test discovery with reflection. 234 235 if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) { 236 throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover); 237 } 238 239 if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) { 240 throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover); 241 } 242 243 if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) { 244 throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic)); 245 } 246 247 if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) { 248 throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic)); 249 } 250 251 // Test discovery with JNI. 252 253 if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) { 254 throwDiscoveryException(klass, name, true, "JNI", canDiscover); 255 } 256 257 // Test discovery with MethodHandles.lookup() which is caller 258 // context sensitive. 259 260 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 261 if (JLI.canDiscoverWithLookupFindGetter(lookup, klass, name, int.class) 262 != canDiscover) { 263 throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findGetter()", 264 canDiscover); 265 } 266 if (JLI.canDiscoverWithLookupFindStaticGetter(lookup, klass, name, int.class) 267 != canDiscover) { 268 throwDiscoveryException(klass, name, true, "MethodHandles.lookup().findStaticGetter()", 269 canDiscover); 270 } 271 272 // Test discovery with MethodHandles.publicLookup() which can only 273 // see public fields. Looking up setters here and fields in 274 // interfaces are implicitly final. 275 276 final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 277 if (JLI.canDiscoverWithLookupFindSetter(publicLookup, klass, name, int.class) 278 != canDiscover) { 279 throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findSetter()", 280 canDiscover); 281 } 282 if (JLI.canDiscoverWithLookupFindStaticSetter(publicLookup, klass, name, int.class) 283 != canDiscover) { 284 throwDiscoveryException(klass, name, true, "MethodHandles.publicLookup().findStaticSetter()", 285 canDiscover); 286 } 287 288 // Check for meta reflection. 289 290 // With hidden api check hardening enabled, only white and light greylisted fields should be 291 // discoverable. 292 if (Reflection.canDiscoverFieldWithMetaReflection(klass, name, true) != canDiscover) { 293 throwDiscoveryException(klass, name, false, 294 "Meta reflection with hidden api hardening enabled", canDiscover); 295 } 296 297 if (testHiddenApiCheckHardeningDisabled) { 298 // With hidden api check hardening disabled, all fields should be discoverable. 299 if (Reflection.canDiscoverFieldWithMetaReflection(klass, name, false) != true) { 300 throwDiscoveryException(klass, name, false, 301 "Meta reflection with hidden api hardening enabled", canDiscover); 302 } 303 } 304 305 if (canDiscover) { 306 // Test that modifiers are unaffected. 307 308 if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { 309 throwModifiersException(klass, name, true); 310 } 311 312 // Test getters and setters when meaningful. 313 314 if (!Reflection.canGetField(klass, name)) { 315 throwAccessException(klass, name, true, "Field.getInt()"); 316 } 317 if (!Reflection.canSetField(klass, name)) { 318 throwAccessException(klass, name, true, "Field.setInt()"); 319 } 320 if (!JNI.canGetField(klass, name, isStatic)) { 321 throwAccessException(klass, name, true, "getIntField"); 322 } 323 if (!JNI.canSetField(klass, name, isStatic)) { 324 throwAccessException(klass, name, true, "setIntField"); 325 } 326 } 327 328 // Test that callbacks are invoked correctly. 329 checkMemberCallback(klass, name, isPublic, true /* isField */, invokesMemberCallback); 330 } 331 checkMethod(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, boolean testHiddenApiCheckHardeningDisabled)332 private static void checkMethod(Class<?> klass, String name, boolean isStatic, 333 Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback, 334 boolean testHiddenApiCheckHardeningDisabled) throws Exception { 335 336 boolean isPublic = (visibility == Visibility.Public); 337 if (klass.isInterface() && !isPublic) { 338 // All interface members are public. 339 return; 340 } 341 342 boolean canDiscover = (behaviour != Behaviour.Denied); 343 344 // Test discovery with reflection. 345 346 if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) { 347 throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover); 348 } 349 350 if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) { 351 throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover); 352 } 353 354 if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) { 355 throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic)); 356 } 357 358 if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) { 359 throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic)); 360 } 361 362 // Test discovery with JNI. 363 364 if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) { 365 throwDiscoveryException(klass, name, false, "JNI", canDiscover); 366 } 367 368 // Test discovery with MethodHandles.lookup(). 369 370 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 371 final MethodType methodType = MethodType.methodType(int.class); 372 if (JLI.canDiscoverWithLookupFindVirtual(lookup, klass, name, methodType) != canDiscover) { 373 throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findVirtual()", 374 canDiscover); 375 } 376 377 if (JLI.canDiscoverWithLookupFindStatic(lookup, klass, name, methodType) != canDiscover) { 378 throwDiscoveryException(klass, name, false, "MethodHandles.lookup().findStatic()", 379 canDiscover); 380 } 381 382 // Check for meta reflection. 383 384 // With hidden api check hardening enabled, only white and light greylisted methods should be 385 // discoverable. 386 if (Reflection.canDiscoverMethodWithMetaReflection(klass, name, true) != canDiscover) { 387 throwDiscoveryException(klass, name, false, 388 "Meta reflection with hidden api hardening enabled", canDiscover); 389 } 390 391 if (testHiddenApiCheckHardeningDisabled) { 392 // With hidden api check hardening disabled, all methods should be discoverable. 393 if (Reflection.canDiscoverMethodWithMetaReflection(klass, name, false) != true) { 394 throwDiscoveryException(klass, name, false, 395 "Meta reflection with hidden api hardening enabled", canDiscover); 396 } 397 } 398 399 // Finish here if we could not discover the method. 400 401 if (canDiscover) { 402 // Test that modifiers are unaffected. 403 404 if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { 405 throwModifiersException(klass, name, false); 406 } 407 408 // Test whether we can invoke the method. This skips non-static interface methods. 409 if (!klass.isInterface() || isStatic) { 410 if (!Reflection.canInvokeMethod(klass, name)) { 411 throwAccessException(klass, name, false, "invoke()"); 412 } 413 if (!JNI.canInvokeMethodA(klass, name, isStatic)) { 414 throwAccessException(klass, name, false, "CallMethodA"); 415 } 416 if (!JNI.canInvokeMethodV(klass, name, isStatic)) { 417 throwAccessException(klass, name, false, "CallMethodV"); 418 } 419 } 420 } 421 422 // Test that callbacks are invoked correctly. 423 checkMemberCallback(klass, name, isPublic, false /* isField */, invokesMemberCallback); 424 } 425 checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, Behaviour behaviour, boolean testHiddenApiCheckHardeningDisabled)426 private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, 427 Behaviour behaviour, boolean testHiddenApiCheckHardeningDisabled) throws Exception { 428 429 boolean isPublic = (visibility == Visibility.Public); 430 String signature = "(" + visibility.mAssociatedType.mShorty + 431 hiddenness.mAssociatedType.mShorty + ")V"; 432 String fullName = "<init>" + signature; 433 Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass, 434 hiddenness.mAssociatedType.mClass }; 435 Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue, 436 hiddenness.mAssociatedType.mDefaultValue }; 437 MethodType methodType = MethodType.methodType(void.class, args); 438 439 boolean canDiscover = (behaviour != Behaviour.Denied); 440 441 // Test discovery with reflection. 442 443 if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) { 444 throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover); 445 } 446 447 if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) { 448 throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover); 449 } 450 451 if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) { 452 throwDiscoveryException( 453 klass, fullName, false, "getConstructor()", (canDiscover && isPublic)); 454 } 455 456 if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) { 457 throwDiscoveryException( 458 klass, fullName, false, "getConstructors()", (canDiscover && isPublic)); 459 } 460 461 // Test discovery with JNI. 462 463 if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) { 464 throwDiscoveryException(klass, fullName, false, "JNI", canDiscover); 465 } 466 467 // Test discovery with MethodHandles.lookup() 468 469 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 470 if (JLI.canDiscoverWithLookupFindConstructor(lookup, klass, methodType) != canDiscover) { 471 throwDiscoveryException(klass, fullName, false, "MethodHandles.lookup().findConstructor", 472 canDiscover); 473 } 474 475 final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 476 if (JLI.canDiscoverWithLookupFindConstructor(publicLookup, klass, methodType) != canDiscover) { 477 throwDiscoveryException(klass, fullName, false, 478 "MethodHandles.publicLookup().findConstructor", 479 canDiscover); 480 } 481 482 // Check for meta reflection. 483 484 // With hidden api check hardening enabled, only white and light greylisted constructors should 485 // be discoverable. 486 if (Reflection.canDiscoverConstructorWithMetaReflection(klass, args, true) != canDiscover) { 487 throwDiscoveryException(klass, fullName, false, 488 "Meta reflection with hidden api hardening enabled", canDiscover); 489 } 490 491 if (testHiddenApiCheckHardeningDisabled) { 492 // With hidden api check hardening disabled, all constructors should be discoverable. 493 if (Reflection.canDiscoverConstructorWithMetaReflection(klass, args, false) != true) { 494 throwDiscoveryException(klass, fullName, false, 495 "Meta reflection with hidden api hardening enabled", canDiscover); 496 } 497 } 498 499 if (canDiscover) { 500 // Test whether we can invoke the constructor. 501 502 if (!Reflection.canInvokeConstructor(klass, args, initargs)) { 503 throwAccessException(klass, fullName, false, "invoke()"); 504 } 505 if (!JNI.canInvokeConstructorA(klass, signature)) { 506 throwAccessException(klass, fullName, false, "NewObjectA"); 507 } 508 if (!JNI.canInvokeConstructorV(klass, signature)) { 509 throwAccessException(klass, fullName, false, "NewObjectV"); 510 } 511 } 512 } 513 checkNullaryConstructor(Class<?> klass, Behaviour behaviour)514 private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour) 515 throws Exception { 516 boolean canAccess = (behaviour != Behaviour.Denied); 517 518 if (Reflection.canUseNewInstance(klass) != canAccess) { 519 throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + 520 "be able to construct " + klass.getName() + ". " + configMessage); 521 } 522 } 523 checkLinking(String className, boolean takesParameter, Behaviour behaviour)524 private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour) 525 throws Exception { 526 boolean canAccess = (behaviour != Behaviour.Denied); 527 528 if (Linking.canAccess(className, takesParameter) != canAccess) { 529 throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + 530 "be able to verify " + className + "." + configMessage); 531 } 532 } 533 throwDiscoveryException(Class<?> klass, String name, boolean isField, String fn, boolean canAccess)534 private static void throwDiscoveryException(Class<?> klass, String name, boolean isField, 535 String fn, boolean canAccess) { 536 throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + 537 "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " + 538 configMessage); 539 } 540 throwAccessException(Class<?> klass, String name, boolean isField, String fn)541 private static void throwAccessException(Class<?> klass, String name, boolean isField, 542 String fn) { 543 throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") + 544 klass.getName() + "." + name + " using " + fn + ". " + configMessage); 545 } 546 throwModifiersException(Class<?> klass, String name, boolean isField)547 private static void throwModifiersException(Class<?> klass, String name, boolean isField) { 548 throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + 549 "." + name + " to not expose hidden modifiers"); 550 } 551 552 private static DexDomain parentDomain; 553 private static DexDomain childDomain; 554 private static boolean everythingSdked; 555 556 private static String configMessage; 557 } 558