1 /* 2 * Copyright (C) 2013 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 package com.android.server.firewall; 18 19 import android.annotation.NonNull; 20 import android.app.AppGlobals; 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.IPackageManager; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManagerInternal; 28 import android.os.Binder; 29 import android.os.Environment; 30 import android.os.FileObserver; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.RemoteException; 35 import android.util.ArrayMap; 36 import android.util.Slog; 37 import android.util.Xml; 38 39 import com.android.internal.util.ArrayUtils; 40 import com.android.internal.util.XmlUtils; 41 import com.android.server.EventLogTags; 42 import com.android.server.IntentResolver; 43 import com.android.server.LocalServices; 44 import com.android.server.pm.Computer; 45 46 import org.xmlpull.v1.XmlPullParser; 47 import org.xmlpull.v1.XmlPullParserException; 48 49 import java.io.File; 50 import java.io.FileInputStream; 51 import java.io.FileNotFoundException; 52 import java.io.IOException; 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.HashMap; 56 import java.util.List; 57 58 public class IntentFirewall { 59 static final String TAG = "IntentFirewall"; 60 61 // e.g. /data/system/ifw or /data/secure/system/ifw 62 private static final File RULES_DIR = new File(Environment.getDataSystemDirectory(), "ifw"); 63 64 private static final int LOG_PACKAGES_MAX_LENGTH = 150; 65 private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125; 66 67 private static final String TAG_RULES = "rules"; 68 private static final String TAG_ACTIVITY = "activity"; 69 private static final String TAG_SERVICE = "service"; 70 private static final String TAG_BROADCAST = "broadcast"; 71 72 private static final int TYPE_ACTIVITY = 0; 73 private static final int TYPE_BROADCAST = 1; 74 private static final int TYPE_SERVICE = 2; 75 76 private static final HashMap<String, FilterFactory> factoryMap; 77 78 private final AMSInterface mAms; 79 80 private final RuleObserver mObserver; 81 82 @NonNull 83 private PackageManagerInternal mPackageManager; 84 85 private FirewallIntentResolver mActivityResolver = new FirewallIntentResolver(); 86 private FirewallIntentResolver mBroadcastResolver = new FirewallIntentResolver(); 87 private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver(); 88 89 static { 90 FilterFactory[] factories = new FilterFactory[] { 91 AndFilter.FACTORY, 92 OrFilter.FACTORY, 93 NotFilter.FACTORY, 94 95 StringFilter.ACTION, 96 StringFilter.COMPONENT, 97 StringFilter.COMPONENT_NAME, 98 StringFilter.COMPONENT_PACKAGE, 99 StringFilter.DATA, 100 StringFilter.HOST, 101 StringFilter.MIME_TYPE, 102 StringFilter.SCHEME, 103 StringFilter.PATH, 104 StringFilter.SSP, 105 106 CategoryFilter.FACTORY, 107 SenderFilter.FACTORY, 108 SenderPackageFilter.FACTORY, 109 SenderPermissionFilter.FACTORY, 110 PortFilter.FACTORY 111 }; 112 113 // load factor ~= .75 114 factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3); 115 for (int i=0; i<factories.length; i++) { 116 FilterFactory factory = factories[i]; factory.getTagName()117 factoryMap.put(factory.getTagName(), factory); 118 } 119 } 120 IntentFirewall(AMSInterface ams, Handler handler)121 public IntentFirewall(AMSInterface ams, Handler handler) { 122 mAms = ams; 123 mHandler = new FirewallHandler(handler.getLooper()); 124 File rulesDir = getRulesDir(); 125 rulesDir.mkdirs(); 126 127 readRulesDir(rulesDir); 128 129 mObserver = new RuleObserver(rulesDir); 130 mObserver.startWatching(); 131 } 132 getPackageManager()133 PackageManagerInternal getPackageManager() { 134 if (mPackageManager == null) { 135 mPackageManager = LocalServices.getService(PackageManagerInternal.class); 136 } 137 return mPackageManager; 138 } 139 140 /** 141 * This is called from ActivityManager to check if a start activity intent should be allowed. 142 * It is assumed the caller is already holding the global ActivityManagerService lock. 143 */ checkStartActivity(Intent intent, int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp)144 public boolean checkStartActivity(Intent intent, int callerUid, int callerPid, 145 String resolvedType, ApplicationInfo resolvedApp) { 146 return checkIntent(mActivityResolver, intent.getComponent(), TYPE_ACTIVITY, intent, 147 callerUid, callerPid, resolvedType, resolvedApp.uid); 148 } 149 checkService(ComponentName resolvedService, Intent intent, int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp)150 public boolean checkService(ComponentName resolvedService, Intent intent, int callerUid, 151 int callerPid, String resolvedType, ApplicationInfo resolvedApp) { 152 return checkIntent(mServiceResolver, resolvedService, TYPE_SERVICE, intent, callerUid, 153 callerPid, resolvedType, resolvedApp.uid); 154 } 155 checkBroadcast(Intent intent, int callerUid, int callerPid, String resolvedType, int receivingUid)156 public boolean checkBroadcast(Intent intent, int callerUid, int callerPid, 157 String resolvedType, int receivingUid) { 158 return checkIntent(mBroadcastResolver, intent.getComponent(), TYPE_BROADCAST, intent, 159 callerUid, callerPid, resolvedType, receivingUid); 160 } 161 checkIntent(FirewallIntentResolver resolver, ComponentName resolvedComponent, int intentType, Intent intent, int callerUid, int callerPid, String resolvedType, int receivingUid)162 public boolean checkIntent(FirewallIntentResolver resolver, ComponentName resolvedComponent, 163 int intentType, Intent intent, int callerUid, int callerPid, String resolvedType, 164 int receivingUid) { 165 boolean log = false; 166 boolean block = false; 167 168 // For the first pass, find all the rules that have at least one intent-filter or 169 // component-filter that matches this intent 170 List<Rule> candidateRules; 171 candidateRules = resolver.queryIntent(getPackageManager().snapshot(), intent, resolvedType, 172 false /*defaultOnly*/, 0); 173 if (candidateRules == null) { 174 candidateRules = new ArrayList<Rule>(); 175 } 176 resolver.queryByComponent(resolvedComponent, candidateRules); 177 178 // For the second pass, try to match the potentially more specific conditions in each 179 // rule against the intent 180 for (int i=0; i<candidateRules.size(); i++) { 181 Rule rule = candidateRules.get(i); 182 if (rule.matches(this, resolvedComponent, intent, callerUid, callerPid, resolvedType, 183 receivingUid)) { 184 block |= rule.getBlock(); 185 log |= rule.getLog(); 186 187 // if we've already determined that we should both block and log, there's no need 188 // to continue trying rules 189 if (block && log) { 190 break; 191 } 192 } 193 } 194 195 if (log) { 196 logIntent(intentType, intent, callerUid, resolvedType); 197 } 198 199 return !block; 200 } 201 logIntent(int intentType, Intent intent, int callerUid, String resolvedType)202 private static void logIntent(int intentType, Intent intent, int callerUid, 203 String resolvedType) { 204 // The component shouldn't be null, but let's double check just to be safe 205 ComponentName cn = intent.getComponent(); 206 String shortComponent = null; 207 if (cn != null) { 208 shortComponent = cn.flattenToShortString(); 209 } 210 211 String callerPackages = null; 212 int callerPackageCount = 0; 213 IPackageManager pm = AppGlobals.getPackageManager(); 214 if (pm != null) { 215 try { 216 String[] callerPackagesArray = pm.getPackagesForUid(callerUid); 217 if (callerPackagesArray != null) { 218 callerPackageCount = callerPackagesArray.length; 219 callerPackages = joinPackages(callerPackagesArray); 220 } 221 } catch (RemoteException ex) { 222 Slog.e(TAG, "Remote exception while retrieving packages", ex); 223 } 224 } 225 226 EventLogTags.writeIfwIntentMatched(intentType, shortComponent, callerUid, 227 callerPackageCount, callerPackages, intent.getAction(), resolvedType, 228 intent.getDataString(), intent.getFlags()); 229 } 230 231 /** 232 * Joins a list of package names such that the resulting string is no more than 233 * LOG_PACKAGES_MAX_LENGTH. 234 * 235 * Only full package names will be added to the result, unless every package is longer than the 236 * limit, in which case one of the packages will be truncated and added. In this case, an 237 * additional '-' character will be added to the end of the string, to denote the truncation. 238 * 239 * If it encounters a package that won't fit in the remaining space, it will continue on to the 240 * next package, unless the total length of the built string so far is greater than 241 * LOG_PACKAGES_SUFFICIENT_LENGTH, in which case it will stop and return what it has. 242 */ joinPackages(String[] packages)243 private static String joinPackages(String[] packages) { 244 boolean first = true; 245 StringBuilder sb = new StringBuilder(); 246 for (int i=0; i<packages.length; i++) { 247 String pkg = packages[i]; 248 249 // + 1 length for the comma. This logic technically isn't correct for the first entry, 250 // but it's not critical. 251 if (sb.length() + pkg.length() + 1 < LOG_PACKAGES_MAX_LENGTH) { 252 if (!first) { 253 sb.append(','); 254 } else { 255 first = false; 256 } 257 sb.append(pkg); 258 } else if (sb.length() >= LOG_PACKAGES_SUFFICIENT_LENGTH) { 259 return sb.toString(); 260 } 261 } 262 if (sb.length() == 0 && packages.length > 0) { 263 String pkg = packages[0]; 264 // truncating from the end - the last part of the package name is more likely to be 265 // interesting/unique 266 return pkg.substring(pkg.length() - LOG_PACKAGES_MAX_LENGTH + 1) + '-'; 267 } 268 return null; 269 } 270 getRulesDir()271 public static File getRulesDir() { 272 return RULES_DIR; 273 } 274 275 /** 276 * Reads rules from all xml files (*.xml) in the given directory, and replaces our set of rules 277 * with the newly read rules. 278 * 279 * We only check for files ending in ".xml", to allow for temporary files that are atomically 280 * renamed to .xml 281 * 282 * All calls to this method from the file observer come through a handler and are inherently 283 * serialized 284 */ readRulesDir(File rulesDir)285 private void readRulesDir(File rulesDir) { 286 FirewallIntentResolver[] resolvers = new FirewallIntentResolver[3]; 287 for (int i=0; i<resolvers.length; i++) { 288 resolvers[i] = new FirewallIntentResolver(); 289 } 290 291 File[] files = rulesDir.listFiles(); 292 if (files != null) { 293 for (int i=0; i<files.length; i++) { 294 File file = files[i]; 295 296 if (file.getName().endsWith(".xml")) { 297 readRules(file, resolvers); 298 } 299 } 300 } 301 302 Slog.i(TAG, "Read new rules (A:" + resolvers[TYPE_ACTIVITY].filterSet().size() + 303 " B:" + resolvers[TYPE_BROADCAST].filterSet().size() + 304 " S:" + resolvers[TYPE_SERVICE].filterSet().size() + ")"); 305 306 synchronized (mAms.getAMSLock()) { 307 mActivityResolver = resolvers[TYPE_ACTIVITY]; 308 mBroadcastResolver = resolvers[TYPE_BROADCAST]; 309 mServiceResolver = resolvers[TYPE_SERVICE]; 310 } 311 } 312 313 /** 314 * Reads rules from the given file and add them to the given resolvers 315 */ readRules(File rulesFile, FirewallIntentResolver[] resolvers)316 private void readRules(File rulesFile, FirewallIntentResolver[] resolvers) { 317 // some temporary lists to hold the rules while we parse the xml file, so that we can 318 // add the rules all at once, after we know there weren't any major structural problems 319 // with the xml file 320 List<List<Rule>> rulesByType = new ArrayList<List<Rule>>(3); 321 for (int i=0; i<3; i++) { 322 rulesByType.add(new ArrayList<Rule>()); 323 } 324 325 FileInputStream fis; 326 try { 327 fis = new FileInputStream(rulesFile); 328 } catch (FileNotFoundException ex) { 329 // Nope, no rules. Nothing else to do! 330 return; 331 } 332 333 try { 334 XmlPullParser parser = Xml.newPullParser(); 335 336 parser.setInput(fis, null); 337 338 XmlUtils.beginDocument(parser, TAG_RULES); 339 340 int outerDepth = parser.getDepth(); 341 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 342 int ruleType = -1; 343 344 String tagName = parser.getName(); 345 if (tagName.equals(TAG_ACTIVITY)) { 346 ruleType = TYPE_ACTIVITY; 347 } else if (tagName.equals(TAG_BROADCAST)) { 348 ruleType = TYPE_BROADCAST; 349 } else if (tagName.equals(TAG_SERVICE)) { 350 ruleType = TYPE_SERVICE; 351 } 352 353 if (ruleType != -1) { 354 Rule rule = new Rule(); 355 356 List<Rule> rules = rulesByType.get(ruleType); 357 358 // if we get an error while parsing a particular rule, we'll just ignore 359 // that rule and continue on with the next rule 360 try { 361 rule.readFromXml(parser); 362 } catch (XmlPullParserException ex) { 363 Slog.e(TAG, "Error reading an intent firewall rule from " + rulesFile, ex); 364 continue; 365 } 366 367 rules.add(rule); 368 } 369 } 370 } catch (XmlPullParserException ex) { 371 // if there was an error outside of a specific rule, then there are probably 372 // structural problems with the xml file, and we should completely ignore it 373 Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex); 374 return; 375 } catch (IOException ex) { 376 Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex); 377 return; 378 } finally { 379 try { 380 fis.close(); 381 } catch (IOException ex) { 382 Slog.e(TAG, "Error while closing " + rulesFile, ex); 383 } 384 } 385 386 for (int ruleType=0; ruleType<rulesByType.size(); ruleType++) { 387 List<Rule> rules = rulesByType.get(ruleType); 388 FirewallIntentResolver resolver = resolvers[ruleType]; 389 390 for (int ruleIndex=0; ruleIndex<rules.size(); ruleIndex++) { 391 Rule rule = rules.get(ruleIndex); 392 for (int i=0; i<rule.getIntentFilterCount(); i++) { 393 resolver.addFilter(null, rule.getIntentFilter(i)); 394 } 395 for (int i=0; i<rule.getComponentFilterCount(); i++) { 396 resolver.addComponentFilter(rule.getComponentFilter(i), rule); 397 } 398 } 399 } 400 } 401 parseFilter(XmlPullParser parser)402 static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException { 403 String elementName = parser.getName(); 404 405 FilterFactory factory = factoryMap.get(elementName); 406 407 if (factory == null) { 408 throw new XmlPullParserException("Unknown element in filter list: " + elementName); 409 } 410 return factory.newFilter(parser); 411 } 412 413 /** 414 * Represents a single activity/service/broadcast rule within one of the xml files. 415 * 416 * Rules are matched against an incoming intent in two phases. The goal of the first phase 417 * is to select a subset of rules that might match a given intent. 418 * 419 * For the first phase, we use a combination of intent filters (via an IntentResolver) 420 * and component filters to select which rules to check. If a rule has multiple intent or 421 * component filters, only a single filter must match for the rule to be passed on to the 422 * second phase. 423 * 424 * In the second phase, we check the specific conditions in each rule against the values in the 425 * intent. All top level conditions (but not filters) in the rule must match for the rule as a 426 * whole to match. 427 * 428 * If the rule matches, then we block or log the intent, as specified by the rule. If multiple 429 * rules match, we combine the block/log flags from any matching rule. 430 */ 431 private static class Rule extends AndFilter { 432 private static final String TAG_INTENT_FILTER = "intent-filter"; 433 private static final String TAG_COMPONENT_FILTER = "component-filter"; 434 private static final String ATTR_NAME = "name"; 435 436 private static final String ATTR_BLOCK = "block"; 437 private static final String ATTR_LOG = "log"; 438 439 private final ArrayList<FirewallIntentFilter> mIntentFilters = 440 new ArrayList<FirewallIntentFilter>(1); 441 private final ArrayList<ComponentName> mComponentFilters = new ArrayList<ComponentName>(0); 442 private boolean block; 443 private boolean log; 444 445 @Override readFromXml(XmlPullParser parser)446 public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { 447 block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK)); 448 log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG)); 449 450 super.readFromXml(parser); 451 return this; 452 } 453 454 @Override readChild(XmlPullParser parser)455 protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { 456 String currentTag = parser.getName(); 457 458 if (currentTag.equals(TAG_INTENT_FILTER)) { 459 FirewallIntentFilter intentFilter = new FirewallIntentFilter(this); 460 intentFilter.readFromXml(parser); 461 mIntentFilters.add(intentFilter); 462 } else if (currentTag.equals(TAG_COMPONENT_FILTER)) { 463 String componentStr = parser.getAttributeValue(null, ATTR_NAME); 464 if (componentStr == null) { 465 throw new XmlPullParserException("Component name must be specified.", 466 parser, null); 467 } 468 469 ComponentName componentName = ComponentName.unflattenFromString(componentStr); 470 if (componentName == null) { 471 throw new XmlPullParserException("Invalid component name: " + componentStr); 472 } 473 474 mComponentFilters.add(componentName); 475 } else { 476 super.readChild(parser); 477 } 478 } 479 getIntentFilterCount()480 public int getIntentFilterCount() { 481 return mIntentFilters.size(); 482 } 483 getIntentFilter(int index)484 public FirewallIntentFilter getIntentFilter(int index) { 485 return mIntentFilters.get(index); 486 } 487 getComponentFilterCount()488 public int getComponentFilterCount() { 489 return mComponentFilters.size(); 490 } 491 getComponentFilter(int index)492 public ComponentName getComponentFilter(int index) { 493 return mComponentFilters.get(index); 494 } getBlock()495 public boolean getBlock() { 496 return block; 497 } 498 getLog()499 public boolean getLog() { 500 return log; 501 } 502 } 503 504 private static class FirewallIntentFilter extends IntentFilter { 505 private final Rule rule; 506 FirewallIntentFilter(Rule rule)507 public FirewallIntentFilter(Rule rule) { 508 this.rule = rule; 509 } 510 } 511 512 private static class FirewallIntentResolver 513 extends IntentResolver<FirewallIntentFilter, Rule> { 514 @Override allowFilterResult(FirewallIntentFilter filter, List<Rule> dest)515 protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) { 516 return !dest.contains(filter.rule); 517 } 518 519 @Override isPackageForFilter(String packageName, FirewallIntentFilter filter)520 protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) { 521 return true; 522 } 523 524 @Override newArray(int size)525 protected FirewallIntentFilter[] newArray(int size) { 526 return new FirewallIntentFilter[size]; 527 } 528 529 @Override newResult(@onNull Computer computer, FirewallIntentFilter filter, int match, int userId, long customFlags)530 protected Rule newResult(@NonNull Computer computer, FirewallIntentFilter filter, 531 int match, int userId, long customFlags) { 532 return filter.rule; 533 } 534 535 @Override sortResults(List<Rule> results)536 protected void sortResults(List<Rule> results) { 537 // there's no need to sort the results 538 return; 539 } 540 541 @Override getIntentFilter(@onNull FirewallIntentFilter input)542 protected IntentFilter getIntentFilter(@NonNull FirewallIntentFilter input) { 543 return input; 544 } 545 queryByComponent(ComponentName componentName, List<Rule> candidateRules)546 public void queryByComponent(ComponentName componentName, List<Rule> candidateRules) { 547 Rule[] rules = mRulesByComponent.get(componentName); 548 if (rules != null) { 549 candidateRules.addAll(Arrays.asList(rules)); 550 } 551 } 552 addComponentFilter(ComponentName componentName, Rule rule)553 public void addComponentFilter(ComponentName componentName, Rule rule) { 554 Rule[] rules = mRulesByComponent.get(componentName); 555 rules = ArrayUtils.appendElement(Rule.class, rules, rule); 556 mRulesByComponent.put(componentName, rules); 557 } 558 559 private final ArrayMap<ComponentName, Rule[]> mRulesByComponent = 560 new ArrayMap<ComponentName, Rule[]>(0); 561 } 562 563 final FirewallHandler mHandler; 564 565 private final class FirewallHandler extends Handler { FirewallHandler(Looper looper)566 public FirewallHandler(Looper looper) { 567 super(looper, null, true); 568 } 569 570 @Override handleMessage(Message msg)571 public void handleMessage(Message msg) { 572 readRulesDir(getRulesDir()); 573 } 574 }; 575 576 /** 577 * Monitors for the creation/deletion/modification of any .xml files in the rule directory 578 */ 579 private class RuleObserver extends FileObserver { 580 private static final int MONITORED_EVENTS = FileObserver.CREATE|FileObserver.MOVED_TO| 581 FileObserver.CLOSE_WRITE|FileObserver.DELETE|FileObserver.MOVED_FROM; 582 RuleObserver(File monitoredDir)583 public RuleObserver(File monitoredDir) { 584 super(monitoredDir.getAbsolutePath(), MONITORED_EVENTS); 585 } 586 587 @Override onEvent(int event, String path)588 public void onEvent(int event, String path) { 589 if (path != null && path.endsWith(".xml")) { 590 // we wait 250ms before taking any action on an event, in order to dedup multiple 591 // events. E.g. a delete event followed by a create event followed by a subsequent 592 // write+close event 593 mHandler.removeMessages(0); 594 mHandler.sendEmptyMessageDelayed(0, 250); 595 } 596 } 597 } 598 599 /** 600 * This interface contains the methods we need from ActivityManagerService. This allows AMS to 601 * export these methods to us without making them public, and also makes it easier to test this 602 * component. 603 */ 604 public interface AMSInterface { checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported)605 int checkComponentPermission(String permission, int pid, int uid, 606 int owningUid, boolean exported); getAMSLock()607 Object getAMSLock(); 608 } 609 610 /** 611 * Checks if the caller has access to a component 612 * 613 * @param permission If present, the caller must have this permission 614 * @param pid The pid of the caller 615 * @param uid The uid of the caller 616 * @param owningUid The uid of the application that owns the component 617 * @param exported Whether the component is exported 618 * @return True if the caller can access the described component 619 */ checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported)620 boolean checkComponentPermission(String permission, int pid, int uid, int owningUid, 621 boolean exported) { 622 return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) == 623 PackageManager.PERMISSION_GRANTED; 624 } 625 signaturesMatch(int uid1, int uid2)626 boolean signaturesMatch(int uid1, int uid2) { 627 final long token = Binder.clearCallingIdentity(); 628 try { 629 // Compare signatures of two packages for different users. 630 return getPackageManager() 631 .checkUidSignaturesForAllUsers(uid1, uid2) == PackageManager.SIGNATURE_MATCH; 632 } finally { 633 Binder.restoreCallingIdentity(token); 634 } 635 } 636 637 } 638