1 /*
2  * Copyright (C) 2019 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 package android.net;
17 
18 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.content.SharedPreferences;
27 import android.content.pm.PackageManager;
28 import android.net.util.SharedLog;
29 import android.os.Build;
30 import android.os.Environment;
31 import android.os.IBinder;
32 import android.os.Process;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.provider.DeviceConfig;
36 import android.text.format.DateUtils;
37 import android.util.ArraySet;
38 import android.util.Slog;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.io.File;
44 import java.io.PrintWriter;
45 
46 /**
47  * Class used to communicate to the various networking mainline modules running in the network stack
48  * process from {@link com.android.server.SystemServer}.
49  * @hide
50  */
51 public class ConnectivityModuleConnector {
52     private static final String TAG = ConnectivityModuleConnector.class.getSimpleName();
53     private static final String IN_PROCESS_SUFFIX = ".InProcess";
54 
55     private static final String PREFS_FILE = "ConnectivityModuleConnector.xml";
56     private static final String PREF_KEY_LAST_CRASH_TIME = "lastcrash_time";
57     private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval";
58     private static final String CONFIG_MIN_UPTIME_BEFORE_CRASH_MS = "min_uptime_before_crash";
59     private static final String CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH =
60             "always_ratelimit_networkstack_crash";
61 
62     // Even if the network stack is lost, do not crash the system more often than this.
63     // Connectivity would be broken, but if the user needs the device for something urgent
64     // (like calling emergency services) we should not bootloop the device.
65     // This is the default value: the actual value can be adjusted via device config.
66     private static final long DEFAULT_MIN_CRASH_INTERVAL_MS = 6 * DateUtils.HOUR_IN_MILLIS;
67 
68     // Even if the network stack is lost, do not crash the system server if it was less than
69     // this much after boot. This avoids bootlooping the device, and crashes should address very
70     // infrequent failures, not failures on boot.
71     private static final long DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
72 
73     private static ConnectivityModuleConnector sInstance;
74 
75     private Context mContext;
76     @GuardedBy("mLog")
77     private final SharedLog mLog = new SharedLog(TAG);
78     @GuardedBy("mHealthListeners")
79     private final ArraySet<ConnectivityModuleHealthListener> mHealthListeners = new ArraySet<>();
80     @NonNull
81     private final Dependencies mDeps;
82 
ConnectivityModuleConnector()83     private ConnectivityModuleConnector() {
84         this(new DependenciesImpl());
85     }
86 
87     @VisibleForTesting
ConnectivityModuleConnector(@onNull Dependencies deps)88     ConnectivityModuleConnector(@NonNull Dependencies deps) {
89         mDeps = deps;
90     }
91 
92     /**
93      * Get the {@link ConnectivityModuleConnector} singleton instance.
94      */
getInstance()95     public static synchronized ConnectivityModuleConnector getInstance() {
96         if (sInstance == null) {
97             sInstance = new ConnectivityModuleConnector();
98         }
99         return sInstance;
100     }
101 
102     /**
103      * Initialize the network stack connector. Should be called only once on device startup, before
104      * any client attempts to use the network stack.
105      */
init(Context context)106     public void init(Context context) {
107         log("Network stack init");
108         mContext = context;
109     }
110 
111     /**
112      * Callback interface for severe failures of the NetworkStack.
113      *
114      * <p>Useful for health monitors such as PackageWatchdog.
115      */
116     public interface ConnectivityModuleHealthListener {
117         /**
118          * Called when there is a severe failure of the network stack.
119          * @param packageName Package name of the network stack.
120          */
onNetworkStackFailure(@onNull String packageName)121         void onNetworkStackFailure(@NonNull String packageName);
122     }
123 
124     /**
125      * Callback invoked by the connector once the connection to the corresponding module is
126      * established.
127      */
128     public interface ModuleServiceCallback {
129         /**
130          * Invoked when the corresponding service has connected.
131          *
132          * @param iBinder Binder object for the service.
133          */
onModuleServiceConnected(@onNull IBinder iBinder)134         void onModuleServiceConnected(@NonNull IBinder iBinder);
135     }
136 
137     /**
138      * Interface used to determine the intent to use to bind to the module. Useful for testing.
139      */
140     @VisibleForTesting
141     protected interface Dependencies {
142         /**
143          * Determine the intent to use to bind to the module.
144          * @return null if the intent could not be resolved, the intent otherwise.
145          */
146         @Nullable
getModuleServiceIntent( @onNull PackageManager pm, @NonNull String serviceIntentBaseAction, @NonNull String servicePermissionName, boolean inSystemProcess)147         Intent getModuleServiceIntent(
148                 @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
149                 @NonNull String servicePermissionName, boolean inSystemProcess);
150     }
151 
152     private static class DependenciesImpl implements Dependencies {
153         @Nullable
154         @Override
getModuleServiceIntent( @onNull PackageManager pm, @NonNull String serviceIntentBaseAction, @NonNull String servicePermissionName, boolean inSystemProcess)155         public Intent getModuleServiceIntent(
156                 @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
157                 @NonNull String servicePermissionName, boolean inSystemProcess) {
158             final Intent intent =
159                     new Intent(inSystemProcess
160                             ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
161                             : serviceIntentBaseAction);
162             final ComponentName comp = intent.resolveSystemService(pm, 0);
163             if (comp == null) {
164                 return null;
165             }
166             intent.setComponent(comp);
167 
168             final int uid;
169             try {
170                 uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
171             } catch (PackageManager.NameNotFoundException e) {
172                 throw new SecurityException(
173                         "Could not check network stack UID; package not found.", e);
174             }
175 
176             final int expectedUid =
177                     inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
178             if (uid != expectedUid) {
179                 throw new SecurityException("Invalid network stack UID: " + uid);
180             }
181 
182             if (!inSystemProcess) {
183                 checkModuleServicePermission(pm, comp, servicePermissionName);
184             }
185 
186             return intent;
187         }
188     }
189 
190 
191     /**
192      * Add a {@link ConnectivityModuleHealthListener} to listen to network stack health events.
193      */
registerHealthListener(@onNull ConnectivityModuleHealthListener listener)194     public void registerHealthListener(@NonNull ConnectivityModuleHealthListener listener) {
195         synchronized (mHealthListeners) {
196             mHealthListeners.add(listener);
197         }
198     }
199 
200     /**
201      * Start a module running in the network stack or system_server process. Should be called only
202      * once for each module per device startup.
203      *
204      * <p>This method will start a networking module either in the network stack
205      * process, or inside the system server on devices that do not support the corresponding
206      * mainline network . The corresponding networking module service's binder
207      * object will then be delivered asynchronously via the provided {@link ModuleServiceCallback}.
208      *
209      * @param serviceIntentBaseAction Base action to use for constructing the intent needed to
210      *                                bind to the corresponding module.
211      * @param servicePermissionName Permission to be held by the corresponding module.
212      */
startModuleService( @onNull String serviceIntentBaseAction, @NonNull String servicePermissionName, @NonNull ModuleServiceCallback callback)213     public void startModuleService(
214             @NonNull String serviceIntentBaseAction,
215             @NonNull String servicePermissionName,
216             @NonNull ModuleServiceCallback callback) {
217         log("Starting networking module " + serviceIntentBaseAction);
218 
219         final PackageManager pm = mContext.getPackageManager();
220 
221         // Try to bind in-process if the device was shipped with an in-process version
222         Intent intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
223                 servicePermissionName, true /* inSystemProcess */);
224 
225         // Otherwise use the updatable module version
226         if (intent == null) {
227             intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
228                     servicePermissionName, false /* inSystemProcess */);
229             log("Starting networking module in network_stack process");
230         } else {
231             log("Starting networking module in system_server process");
232         }
233 
234         if (intent == null) {
235             maybeCrashWithTerribleFailure("Could not resolve the networking module", null);
236             return;
237         }
238 
239         final String packageName = intent.getComponent().getPackageName();
240 
241         // Start the network stack. The service will be added to the service manager by the
242         // corresponding client in ModuleServiceCallback.onModuleServiceConnected().
243         if (!mContext.bindServiceAsUser(
244                 intent, new ModuleServiceConnection(packageName, callback),
245                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
246             maybeCrashWithTerribleFailure(
247                     "Could not bind to networking module in-process, or in app with "
248                             + intent, packageName);
249             return;
250         }
251 
252         log("Networking module service start requested");
253     }
254 
255     private class ModuleServiceConnection implements ServiceConnection {
256         @NonNull
257         private final String mPackageName;
258         @NonNull
259         private final ModuleServiceCallback mModuleServiceCallback;
260 
ModuleServiceConnection( @onNull String packageName, @NonNull ModuleServiceCallback moduleCallback)261         private ModuleServiceConnection(
262                 @NonNull String packageName,
263                 @NonNull ModuleServiceCallback moduleCallback) {
264             mPackageName = packageName;
265             mModuleServiceCallback = moduleCallback;
266         }
267 
268         @Override
onServiceConnected(ComponentName name, IBinder service)269         public void onServiceConnected(ComponentName name, IBinder service) {
270             logi("Networking module service connected");
271             mModuleServiceCallback.onModuleServiceConnected(service);
272         }
273 
274         @Override
onServiceDisconnected(ComponentName name)275         public void onServiceDisconnected(ComponentName name) {
276             // onServiceDisconnected is not being called on device shutdown, so this method being
277             // called always indicates a bad state for the system server.
278             // This code path is only run by the system server: only the system server binds
279             // to the NetworkStack as a service. Other processes get the NetworkStack from
280             // the ServiceManager.
281             maybeCrashWithTerribleFailure("Lost network stack", mPackageName);
282         }
283     }
284 
checkModuleServicePermission( @onNull PackageManager pm, @NonNull ComponentName comp, @NonNull String servicePermissionName)285     private static void checkModuleServicePermission(
286             @NonNull PackageManager pm, @NonNull ComponentName comp,
287             @NonNull String servicePermissionName) {
288         final int hasPermission =
289                 pm.checkPermission(servicePermissionName, comp.getPackageName());
290         if (hasPermission != PERMISSION_GRANTED) {
291             throw new SecurityException(
292                     "Networking module does not have permission " + servicePermissionName);
293         }
294     }
295 
maybeCrashWithTerribleFailure(@onNull String message, @Nullable String packageName)296     private synchronized void maybeCrashWithTerribleFailure(@NonNull String message,
297             @Nullable String packageName) {
298         logWtf(message, null);
299         // uptime is monotonic even after a framework restart
300         final long uptime = SystemClock.elapsedRealtime();
301         final long now = System.currentTimeMillis();
302         final long minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
303                 CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS);
304         final long minUptimeBeforeCrash = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
305                 CONFIG_MIN_UPTIME_BEFORE_CRASH_MS, DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS);
306         final boolean alwaysRatelimit = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CONNECTIVITY,
307                 CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH, false);
308 
309         final SharedPreferences prefs = getSharedPreferences();
310         final long lastCrashTime = tryGetLastCrashTime(prefs);
311 
312         // Only crash if there was enough time since boot, and (if known) enough time passed since
313         // the last crash.
314         // time and lastCrashTime may be unreliable if devices have incorrect clock time, but they
315         // are only used to limit the number of crashes compared to only using the time since boot,
316         // which would also be OK behavior by itself.
317         // - If lastCrashTime is incorrectly more than the current time, only look at uptime
318         // - If it is much less than current time, only look at uptime
319         // - If current time is during the next few hours after last crash time, don't crash.
320         //   Considering that this only matters if last boot was some time ago, it's likely that
321         //   time will be set correctly. Otherwise, not crashing is not a big problem anyway. Being
322         //   in this last state would also not last for long since the window is only a few hours.
323         final boolean alwaysCrash = Build.IS_DEBUGGABLE && !alwaysRatelimit;
324         final boolean justBooted = uptime < minUptimeBeforeCrash;
325         final boolean haveLastCrashTime = (lastCrashTime != 0) && (lastCrashTime < now);
326         final boolean haveKnownRecentCrash =
327                 haveLastCrashTime && (now < lastCrashTime + minCrashIntervalMs);
328         if (alwaysCrash || (!justBooted && !haveKnownRecentCrash)) {
329             // The system is not bound to its network stack (for example due to a crash in the
330             // network stack process): better crash rather than stay in a bad state where all
331             // networking is broken.
332             // Using device-encrypted SharedPreferences as DeviceConfig does not have a synchronous
333             // API to persist settings before a crash.
334             tryWriteLastCrashTime(prefs, now);
335             throw new IllegalStateException(message);
336         }
337 
338         // Here the system crashed recently already. Inform listeners that something is
339         // definitely wrong.
340         if (packageName != null) {
341             final ArraySet<ConnectivityModuleHealthListener> listeners;
342             synchronized (mHealthListeners) {
343                 listeners = new ArraySet<>(mHealthListeners);
344             }
345             for (ConnectivityModuleHealthListener listener : listeners) {
346                 listener.onNetworkStackFailure(packageName);
347             }
348         }
349     }
350 
351     @Nullable
352     private SharedPreferences getSharedPreferences() {
353         try {
354             final File prefsFile = new File(
355                     Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE);
356             return mContext.createDeviceProtectedStorageContext()
357                     .getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
358         } catch (Throwable e) {
359             logWtf("Error loading shared preferences", e);
360             return null;
361         }
362     }
363 
364     private long tryGetLastCrashTime(@Nullable SharedPreferences prefs) {
365         if (prefs == null) return 0L;
366         try {
367             return prefs.getLong(PREF_KEY_LAST_CRASH_TIME, 0L);
368         } catch (Throwable e) {
369             logWtf("Error getting last crash time", e);
370             return 0L;
371         }
372     }
373 
374     private void tryWriteLastCrashTime(@Nullable SharedPreferences prefs, long value) {
375         if (prefs == null) return;
376         try {
377             prefs.edit().putLong(PREF_KEY_LAST_CRASH_TIME, value).commit();
378         } catch (Throwable e) {
379             logWtf("Error writing last crash time", e);
380         }
381     }
382 
383     private void log(@NonNull String message) {
384         Slog.d(TAG, message);
385         synchronized (mLog) {
386             mLog.log(message);
387         }
388     }
389 
390     private void logWtf(@NonNull String message, @Nullable Throwable e) {
391         Slog.wtf(TAG, message, e);
392         synchronized (mLog) {
393             mLog.e(message);
394         }
395     }
396 
397     private void loge(@NonNull String message, @Nullable Throwable e) {
398         Slog.e(TAG, message, e);
399         synchronized (mLog) {
400             mLog.e(message);
401         }
402     }
403 
404     private void logi(@NonNull String message) {
405         Slog.i(TAG, message);
406         synchronized (mLog) {
407             mLog.i(message);
408         }
409     }
410 
411     /**
412      * Dump ConnectivityModuleConnector logs to the specified {@link PrintWriter}.
413      */
414     public void dump(PrintWriter pw) {
415         // dump is thread-safe on SharedLog
416         mLog.dump(null, pw, null);
417     }
418 }
419