Skip to content

Commit 003076d

Browse files
authored
Fix potential SecurityException thrown by ConnectivityManager on Android 11 (#2653)
1 parent 0686640 commit 003076d

File tree

3 files changed

+170
-78
lines changed

3 files changed

+170
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Fix timestamp intervals of PerformanceCollectionData in profiles ([#2648](https://github.com/getsentry/sentry-java/pull/2648))
1414
- Fix timestamps of PerformanceCollectionData in profiles ([#2632](https://github.com/getsentry/sentry-java/pull/2632))
1515
- Fix missing propagateMinConstraints flag for SentryTraced ([#2637](https://github.com/getsentry/sentry-java/pull/2637))
16+
- Fix potential SecurityException thrown by ConnectivityManager on Android 11 ([#2653](https://github.com/getsentry/sentry-java/pull/2653))
1617

1718
### Dependencies
1819
- Bump Kotlin compile version from v1.6.10 to 1.8.0 ([#2563](https://github.com/getsentry/sentry-java/pull/2563))

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ConnectivityChecker.java

Lines changed: 96 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
import org.jetbrains.annotations.NotNull;
1515
import org.jetbrains.annotations.Nullable;
1616

17+
/**
18+
* Note: ConnectivityManager sometimes throws SecurityExceptions on Android 11. Hence all relevant
19+
* calls are guarded with try/catch. see https://issuetracker.google.com/issues/175055271 for more
20+
* details
21+
*/
1722
@ApiStatus.Internal
1823
public final class ConnectivityChecker {
1924

@@ -62,13 +67,18 @@ private ConnectivityChecker() {}
6267
logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status.");
6368
return Status.NO_PERMISSION;
6469
}
65-
final android.net.NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
6670

67-
if (activeNetworkInfo == null) {
68-
logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network.");
69-
return Status.NOT_CONNECTED;
71+
try {
72+
final android.net.NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
73+
if (activeNetworkInfo == null) {
74+
logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network.");
75+
return Status.NOT_CONNECTED;
76+
}
77+
return activeNetworkInfo.isConnected() ? Status.CONNECTED : Status.NOT_CONNECTED;
78+
} catch (Throwable t) {
79+
logger.log(SentryLevel.ERROR, "Could not retrieve Connection Status", t);
80+
return Status.UNKNOWN;
7081
}
71-
return activeNetworkInfo.isConnected() ? Status.CONNECTED : Status.NOT_CONNECTED;
7282
}
7383

7484
/**
@@ -93,79 +103,86 @@ private ConnectivityChecker() {}
93103
return null;
94104
}
95105

96-
boolean ethernet = false;
97-
boolean wifi = false;
98-
boolean cellular = false;
106+
try {
107+
boolean ethernet = false;
108+
boolean wifi = false;
109+
boolean cellular = false;
99110

100-
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) {
101-
final Network activeNetwork = connectivityManager.getActiveNetwork();
102-
if (activeNetwork == null) {
103-
logger.log(SentryLevel.INFO, "Network is null and cannot check network status");
104-
return null;
105-
}
106-
final NetworkCapabilities networkCapabilities =
107-
connectivityManager.getNetworkCapabilities(activeNetwork);
108-
if (networkCapabilities == null) {
109-
logger.log(SentryLevel.INFO, "NetworkCapabilities is null and cannot check network type");
110-
return null;
111-
}
112-
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
113-
ethernet = true;
114-
}
115-
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
116-
wifi = true;
117-
}
118-
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
119-
cellular = true;
120-
}
121-
} else {
122-
// ideally using connectivityManager.getAllNetworks(), but its >= Android L only
111+
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) {
123112

124-
// for some reason linting didn't allow a single @SuppressWarnings("deprecation") on method
125-
// signature
126-
@SuppressWarnings("deprecation")
127-
final android.net.NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
113+
final Network activeNetwork = connectivityManager.getActiveNetwork();
114+
if (activeNetwork == null) {
115+
logger.log(SentryLevel.INFO, "Network is null and cannot check network status");
116+
return null;
117+
}
118+
final NetworkCapabilities networkCapabilities =
119+
connectivityManager.getNetworkCapabilities(activeNetwork);
120+
if (networkCapabilities == null) {
121+
logger.log(SentryLevel.INFO, "NetworkCapabilities is null and cannot check network type");
122+
return null;
123+
}
124+
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
125+
ethernet = true;
126+
}
127+
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
128+
wifi = true;
129+
}
130+
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
131+
cellular = true;
132+
}
133+
} else {
134+
// ideally using connectivityManager.getAllNetworks(), but its >= Android L only
128135

129-
if (activeNetworkInfo == null) {
130-
logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network.");
131-
return null;
132-
}
136+
// for some reason linting didn't allow a single @SuppressWarnings("deprecation") on method
137+
// signature
138+
@SuppressWarnings("deprecation")
139+
final android.net.NetworkInfo activeNetworkInfo =
140+
connectivityManager.getActiveNetworkInfo();
133141

134-
@SuppressWarnings("deprecation")
135-
final int type = activeNetworkInfo.getType();
142+
if (activeNetworkInfo == null) {
143+
logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network.");
144+
return null;
145+
}
136146

137-
@SuppressWarnings("deprecation")
138-
final int TYPE_ETHERNET = ConnectivityManager.TYPE_ETHERNET;
147+
@SuppressWarnings("deprecation")
148+
final int type = activeNetworkInfo.getType();
139149

140-
@SuppressWarnings("deprecation")
141-
final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
150+
@SuppressWarnings("deprecation")
151+
final int TYPE_ETHERNET = ConnectivityManager.TYPE_ETHERNET;
142152

143-
@SuppressWarnings("deprecation")
144-
final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
153+
@SuppressWarnings("deprecation")
154+
final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
145155

146-
switch (type) {
147-
case TYPE_ETHERNET:
148-
ethernet = true;
149-
break;
150-
case TYPE_WIFI:
151-
wifi = true;
152-
break;
153-
case TYPE_MOBILE:
154-
cellular = true;
155-
break;
156+
@SuppressWarnings("deprecation")
157+
final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
158+
159+
switch (type) {
160+
case TYPE_ETHERNET:
161+
ethernet = true;
162+
break;
163+
case TYPE_WIFI:
164+
wifi = true;
165+
break;
166+
case TYPE_MOBILE:
167+
cellular = true;
168+
break;
169+
}
156170
}
157-
}
158171

159-
// TODO: change the protocol to be a list of transports as a device may have the capability of
160-
// multiple
161-
if (ethernet) {
162-
return "ethernet";
163-
}
164-
if (wifi) {
165-
return "wifi";
166-
}
167-
if (cellular) {
168-
return "cellular";
172+
// TODO: change the protocol to be a list of transports as a device may have the capability of
173+
// multiple
174+
if (ethernet) {
175+
return "ethernet";
176+
}
177+
if (wifi) {
178+
return "wifi";
179+
}
180+
if (cellular) {
181+
return "cellular";
182+
}
183+
184+
} catch (Throwable exception) {
185+
logger.log(SentryLevel.ERROR, "Failed to retrieve network info", exception);
169186
}
170187

171188
return null;
@@ -228,7 +245,12 @@ public static boolean registerNetworkCallback(
228245
logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status.");
229246
return false;
230247
}
231-
connectivityManager.registerDefaultNetworkCallback(networkCallback);
248+
try {
249+
connectivityManager.registerDefaultNetworkCallback(networkCallback);
250+
} catch (Throwable t) {
251+
logger.log(SentryLevel.ERROR, "registerDefaultNetworkCallback failed", t);
252+
return false;
253+
}
232254
return true;
233255
}
234256

@@ -245,6 +267,10 @@ public static void unregisterNetworkCallback(
245267
if (connectivityManager == null) {
246268
return;
247269
}
248-
connectivityManager.unregisterNetworkCallback(networkCallback);
270+
try {
271+
connectivityManager.unregisterNetworkCallback(networkCallback);
272+
} catch (Throwable t) {
273+
logger.log(SentryLevel.ERROR, "unregisterNetworkCallback failed", t);
274+
}
249275
}
250276
}

sentry-android-core/src/test/java/io/sentry/android/core/ConnectivityCheckerTest.kt

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ class ConnectivityCheckerTest {
5959
@Test
6060
fun `When network is active and connected with permission, return CONNECTED for isConnected`() {
6161
whenever(networkInfo.isConnected).thenReturn(true)
62-
assertEquals(ConnectivityChecker.Status.CONNECTED, ConnectivityChecker.getConnectionStatus(contextMock, mock()))
62+
assertEquals(
63+
ConnectivityChecker.Status.CONNECTED,
64+
ConnectivityChecker.getConnectionStatus(contextMock, mock())
65+
)
6366
}
6467

6568
@Test
@@ -149,30 +152,42 @@ class ConnectivityCheckerTest {
149152
fun `When network capabilities has TRANSPORT_ETHERNET, return ethernet`() {
150153
whenever(networkCapabilities.hasTransport(eq(TRANSPORT_ETHERNET))).thenReturn(true)
151154

152-
assertEquals("ethernet", ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
155+
assertEquals(
156+
"ethernet",
157+
ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo)
158+
)
153159
}
154160

155161
@Test
156162
fun `When network is TYPE_ETHERNET, return ethernet`() {
157163
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
158164
whenever(networkInfo.type).thenReturn(TYPE_ETHERNET)
159165

160-
assertEquals("ethernet", ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
166+
assertEquals(
167+
"ethernet",
168+
ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo)
169+
)
161170
}
162171

163172
@Test
164173
fun `When network capabilities has TRANSPORT_CELLULAR, return cellular`() {
165174
whenever(networkCapabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
166175

167-
assertEquals("cellular", ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
176+
assertEquals(
177+
"cellular",
178+
ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo)
179+
)
168180
}
169181

170182
@Test
171183
fun `When network is TYPE_MOBILE, return cellular`() {
172184
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
173185
whenever(networkInfo.type).thenReturn(TYPE_MOBILE)
174186

175-
assertEquals("cellular", ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
187+
assertEquals(
188+
"cellular",
189+
ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo)
190+
)
176191
}
177192

178193
@Test
@@ -181,7 +196,8 @@ class ConnectivityCheckerTest {
181196
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.N)
182197
whenever(contextMock.getSystemService(any())).thenReturn(connectivityManager)
183198
whenever(contextMock.checkPermission(any(), any(), any())).thenReturn(PERMISSION_DENIED)
184-
val registered = ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
199+
val registered =
200+
ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
185201

186202
assertFalse(registered)
187203
verify(connectivityManager, never()).registerDefaultNetworkCallback(any())
@@ -190,7 +206,8 @@ class ConnectivityCheckerTest {
190206
@Test
191207
fun `When sdkInfoVersion is not min N, do not register any NetworkCallback`() {
192208
whenever(contextMock.getSystemService(any())).thenReturn(connectivityManager)
193-
val registered = ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
209+
val registered =
210+
ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
194211

195212
assertFalse(registered)
196213
verify(connectivityManager, never()).registerDefaultNetworkCallback(any())
@@ -201,7 +218,8 @@ class ConnectivityCheckerTest {
201218
val buildInfo = mock<BuildInfoProvider>()
202219
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.N)
203220
whenever(contextMock.getSystemService(any())).thenReturn(connectivityManager)
204-
val registered = ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
221+
val registered =
222+
ConnectivityChecker.registerNetworkCallback(contextMock, mock(), buildInfo, mock())
205223

206224
assertTrue(registered)
207225
verify(connectivityManager).registerDefaultNetworkCallback(any())
@@ -224,4 +242,51 @@ class ConnectivityCheckerTest {
224242

225243
verify(connectivityManager).unregisterNetworkCallback(any<NetworkCallback>())
226244
}
245+
246+
@Test
247+
fun `When connectivityManager getActiveNetwork throws an exception, getConnectionType returns null`() {
248+
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.S)
249+
whenever(connectivityManager.activeNetwork).thenThrow(SecurityException("Android OS Bug"))
250+
251+
assertNull(ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
252+
}
253+
254+
@Test
255+
fun `When connectivityManager getActiveNetworkInfo throws an exception, getConnectionType returns null`() {
256+
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.KITKAT)
257+
whenever(connectivityManager.activeNetworkInfo).thenThrow(SecurityException("Android OS Bug"))
258+
259+
assertNull(ConnectivityChecker.getConnectionType(contextMock, mock(), buildInfo))
260+
assertEquals(ConnectivityChecker.Status.UNKNOWN, ConnectivityChecker.getConnectionStatus(contextMock, mock()))
261+
}
262+
263+
@Test
264+
fun `When connectivityManager registerDefaultCallback throws an exception, false is returned`() {
265+
whenever(connectivityManager.registerDefaultNetworkCallback(any())).thenThrow(
266+
SecurityException("Android OS Bug")
267+
)
268+
assertFalse(
269+
ConnectivityChecker.registerNetworkCallback(
270+
contextMock,
271+
mock(),
272+
buildInfo,
273+
mock()
274+
)
275+
)
276+
}
277+
278+
@Test
279+
fun `When connectivityManager unregisterDefaultCallback throws an exception, it gets swallowed`() {
280+
whenever(connectivityManager.registerDefaultNetworkCallback(any())).thenThrow(
281+
SecurityException("Android OS Bug")
282+
)
283+
284+
var failed = false
285+
try {
286+
ConnectivityChecker.unregisterNetworkCallback(contextMock, mock(), buildInfo, mock())
287+
} catch (t: Throwable) {
288+
failed = true
289+
}
290+
assertFalse(failed)
291+
}
227292
}

0 commit comments

Comments
 (0)