Skip to content

Commit f54a96c

Browse files
committed
Fix lock deadlocks, production NPE, connection leaks, and log typo
- Wrap all lock.readLock/writeLock operations in try-finally blocks in ServiceSinkhole to prevent permanent deadlocks if exceptions occur while locks are held (e.g. during DB access in blockKnownTracker). Also fixes prepareHostsBlocked where unlock() ran unconditionally even when the lock was never acquired due to early return. - Replace disabled assert + chained NPE in tracker logcat logging: assert is a no-op in production Android, and ipToHost.get(daddr) can return null, causing a crash when log_logcat is enabled. - Fix copy-paste bug in ActivityMain.onActivityResult logging requestCode instead of resultCode. - Add HttpURLConnection.disconnect() in finally blocks in Common.kt and Util.java to prevent socket leaks on repeated network calls. - Move svg.renderToPicture() off the main thread in CountriesFragment; it was executing inside View.post() which runs on the UI thread. https://claude.ai/code/session_011HzyMPo2gd34cnXG4EWLYj
1 parent 58e813d commit f54a96c

5 files changed

Lines changed: 139 additions & 116 deletions

File tree

app/src/main/java/eu/faircode/netguard/ActivityMain.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ public void onDestroy() {
571571

572572
@Override
573573
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
574-
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + requestCode + " ok="
574+
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + resultCode + " ok="
575575
+ (resultCode == RESULT_OK));
576576
Util.logExtras(data);
577577

app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java

Lines changed: 122 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,12 +1683,15 @@ private void startNative(final ParcelFileDescriptor vpn, List<Rule> listAllowed,
16831683
prepareForwarding();
16841684
} else {
16851685
lock.writeLock().lock();
1686-
mapUidAllowed.clear();
1687-
mapUidKnown.clear();
1688-
mapHostsBlocked.clear();
1689-
mapUidIPFilters.clear();
1690-
mapForward.clear();
1691-
lock.writeLock().unlock();
1686+
try {
1687+
mapUidAllowed.clear();
1688+
mapUidKnown.clear();
1689+
mapHostsBlocked.clear();
1690+
mapUidIPFilters.clear();
1691+
mapForward.clear();
1692+
} finally {
1693+
lock.writeLock().unlock();
1694+
}
16921695
}
16931696

16941697
if (log || log_app || filter) {
@@ -1754,26 +1757,30 @@ private void stopNative(ParcelFileDescriptor vpn) {
17541757

17551758
private void unprepare() {
17561759
lock.writeLock().lock();
1757-
mapUidAllowed.clear();
1758-
mapUidKnown.clear();
1759-
mapHostsBlocked.clear();
1760-
mapUidIPFilters.clear();
1761-
mapForward.clear();
1762-
lock.writeLock().unlock();
1760+
try {
1761+
mapUidAllowed.clear();
1762+
mapUidKnown.clear();
1763+
mapHostsBlocked.clear();
1764+
mapUidIPFilters.clear();
1765+
mapForward.clear();
1766+
} finally {
1767+
lock.writeLock().unlock();
1768+
}
17631769
}
17641770

17651771
private void prepareUidAllowed(List<Rule> listAllowed, List<Rule> listRule) {
17661772
lock.writeLock().lock();
1773+
try {
1774+
mapUidAllowed.clear();
1775+
for (Rule rule : listAllowed)
1776+
mapUidAllowed.put(rule.uid, true);
17671777

1768-
mapUidAllowed.clear();
1769-
for (Rule rule : listAllowed)
1770-
mapUidAllowed.put(rule.uid, true);
1771-
1772-
mapUidKnown.clear();
1773-
for (Rule rule : listRule)
1774-
mapUidKnown.put(rule.uid, rule.uid);
1775-
1776-
lock.writeLock().unlock();
1778+
mapUidKnown.clear();
1779+
for (Rule rule : listRule)
1780+
mapUidKnown.put(rule.uid, rule.uid);
1781+
} finally {
1782+
lock.writeLock().unlock();
1783+
}
17771784
}
17781785

17791786
public static void prepareHostsBlocked(Context c) {
@@ -1799,27 +1806,31 @@ public static void prepareHostsBlocked(Context c) {
17991806
}
18001807

18011808
lock.writeLock().lock();
1802-
mapHostsBlocked.clear();
1809+
try {
1810+
mapHostsBlocked.clear();
18031811

1804-
int count = 0;
1805-
br = new BufferedReader(is);
1806-
String line;
1807-
while ((line = br.readLine()) != null) {
1808-
int hash = line.indexOf('#');
1809-
if (hash >= 0)
1810-
line = line.substring(0, hash);
1811-
line = line.trim();
1812-
if (line.length() > 0) {
1813-
String[] words = line.split("\\s+");
1814-
if (words.length == 2) {
1815-
count++;
1816-
mapHostsBlocked.put(words[1], true);
1817-
} else
1818-
Log.i(TAG, "Invalid hosts file line: " + line);
1819-
}
1820-
}
1821-
mapHostsBlocked.put("test.netguard.me", true);
1822-
Log.i(TAG, count + " hosts read");
1812+
int count = 0;
1813+
br = new BufferedReader(is);
1814+
String line;
1815+
while ((line = br.readLine()) != null) {
1816+
int hash = line.indexOf('#');
1817+
if (hash >= 0)
1818+
line = line.substring(0, hash);
1819+
line = line.trim();
1820+
if (line.length() > 0) {
1821+
String[] words = line.split("\\s+");
1822+
if (words.length == 2) {
1823+
count++;
1824+
mapHostsBlocked.put(words[1], true);
1825+
} else
1826+
Log.i(TAG, "Invalid hosts file line: " + line);
1827+
}
1828+
}
1829+
mapHostsBlocked.put("test.netguard.me", true);
1830+
Log.i(TAG, count + " hosts read");
1831+
} finally {
1832+
lock.writeLock().unlock();
1833+
}
18231834
} catch (IOException ex) {
18241835
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
18251836
} finally {
@@ -1837,19 +1848,17 @@ public static void prepareHostsBlocked(Context c) {
18371848
}
18381849
}
18391850

1840-
lock.writeLock().unlock();
1841-
18421851
// Reload TrackerList to ensure it stays in sync with updated hosts
18431852
TrackerList.reloadTrackerData(c);
18441853
}
18451854

18461855
private void prepareUidIPFilters(String dname) {
18471856
lock.writeLock().lock();
1857+
try {
1858+
if (dname == null) // reset mechanism, called from startNative()
1859+
mapUidIPFilters.clear();
18481860

1849-
if (dname == null) // reset mechanism, called from startNative()
1850-
mapUidIPFilters.clear();
1851-
1852-
try (Cursor cursor = DatabaseHelper.getInstance(ServiceSinkhole.this).getAccessDns(dname)) {
1861+
try (Cursor cursor = DatabaseHelper.getInstance(ServiceSinkhole.this).getAccessDns(dname)) {
18531862
int colUid = cursor.getColumnIndex("uid");
18541863
int colVersion = cursor.getColumnIndex("version");
18551864
int colProtocol = cursor.getColumnIndex("protocol");
@@ -1908,13 +1917,15 @@ private void prepareUidIPFilters(String dname) {
19081917
}
19091918
}
19101919
}
1911-
1912-
lock.writeLock().unlock();
1920+
} finally {
1921+
lock.writeLock().unlock();
1922+
}
19131923
}
19141924

19151925
private void prepareForwarding() {
19161926
lock.writeLock().lock();
1917-
mapForward.clear();
1927+
try {
1928+
mapForward.clear();
19181929

19191930
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
19201931
if (prefs.getBoolean("filter", true)) {
@@ -1961,7 +1972,9 @@ private void prepareForwarding() {
19611972
Log.i(TAG, "DoH Forward " + dnsFwd);
19621973
}
19631974
}
1964-
lock.writeLock().unlock();
1975+
} finally {
1976+
lock.writeLock().unlock();
1977+
}
19651978
}
19661979

19671980
private List<Rule> getAllowedRules(List<Rule> listRule) {
@@ -2122,68 +2135,69 @@ public static void clearTrackerCaches() {
21222135
private Allowed isAddressAllowed(Packet packet) {
21232136
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
21242137

2138+
Allowed allowed = null;
21252139
lock.readLock().lock();
2140+
try {
2141+
packet.allowed = false;
2142+
if (prefs.getBoolean("filter", true)) {
2143+
// https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
2144+
if (packet.protocol == 17 /* UDP */ && !prefs.getBoolean("filter_udp", true)) {
2145+
// Allow unfiltered UDP
2146+
packet.allowed = true;
2147+
Log.i(TAG, "Allowing UDP " + packet);
2148+
} else if ((packet.uid < 2000) &&
2149+
!mapUidKnown.containsKey(packet.uid) && isSupported(packet.protocol)) {
2150+
// Allow unknown (system) traffic
2151+
packet.allowed = true;
2152+
Log.w(TAG, "Allowing unknown system " + packet);
2153+
} else if (packet.uid == Process.myUid()) {
2154+
// Allow self
2155+
packet.allowed = true;
2156+
Log.w(TAG, "Allowing self " + packet);
2157+
} else {
2158+
boolean filtered = false;
21262159

2127-
packet.allowed = false;
2128-
if (prefs.getBoolean("filter", true)) {
2129-
// https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
2130-
if (packet.protocol == 17 /* UDP */ && !prefs.getBoolean("filter_udp", true)) {
2131-
// Allow unfiltered UDP
2132-
packet.allowed = true;
2133-
Log.i(TAG, "Allowing UDP " + packet);
2134-
} else if ((packet.uid < 2000) &&
2135-
!mapUidKnown.containsKey(packet.uid) && isSupported(packet.protocol)) {
2136-
// Allow unknown (system) traffic
2137-
packet.allowed = true;
2138-
Log.w(TAG, "Allowing unknown system " + packet);
2139-
} else if (packet.uid == Process.myUid()) {
2140-
// Allow self
2141-
packet.allowed = true;
2142-
Log.w(TAG, "Allowing self " + packet);
2143-
} else {
2144-
boolean filtered = false;
2160+
if (packet.data != null && !packet.data.isEmpty())
2161+
Log.d(TAG, "Found SNI in isAddressAllowed: " + packet.data);
21452162

2146-
if (packet.data != null && !packet.data.isEmpty())
2147-
Log.d(TAG, "Found SNI in isAddressAllowed: " + packet.data);
2163+
// Check if tracker is known
2164+
// In minimal mode (including TC Slim), always enable blocking
2165+
if (blockKnownTracker(packet.daddr, packet.uid)) {
2166+
filtered = true;
2167+
packet.allowed = false;
2168+
}
21482169

2149-
// Check if tracker is known
2150-
// In minimal mode (including TC Slim), always enable blocking
2151-
if (blockKnownTracker(packet.daddr, packet.uid)) {
2152-
filtered = true;
2153-
packet.allowed = false;
2154-
}
2170+
InternetBlocklist internetBlocklist = InternetBlocklist.getInstance(ServiceSinkhole.this);
2171+
if (internetBlocklist.blockedInternet(packet.uid)) {
2172+
filtered = true;
2173+
packet.allowed = false;
2174+
}
21552175

2156-
InternetBlocklist internetBlocklist = InternetBlocklist.getInstance(ServiceSinkhole.this);
2157-
if (internetBlocklist.blockedInternet(packet.uid)) {
2158-
filtered = true;
2159-
packet.allowed = false;
2176+
if (!filtered)
2177+
packet.allowed = true;
21602178
}
2161-
2162-
if (!filtered)
2163-
packet.allowed = true;
21642179
}
2165-
}
21662180

2167-
// Block DNS-over-TLS (DoT) on port 853 to prevent bypassing DNS filtering
2168-
if (packet.allowed && packet.dport == 853 && prefs.getBoolean("block_dot", true)) {
2169-
Log.i(TAG, "Blocking DoT " + packet);
2170-
packet.allowed = false;
2171-
}
2181+
// Block DNS-over-TLS (DoT) on port 853 to prevent bypassing DNS filtering
2182+
if (packet.allowed && packet.dport == 853 && prefs.getBoolean("block_dot", true)) {
2183+
Log.i(TAG, "Blocking DoT " + packet);
2184+
packet.allowed = false;
2185+
}
21722186

2173-
Allowed allowed = null;
2174-
if (packet.allowed)
2175-
if (mapForward.containsKey(packet.dport)) {
2176-
Forward fwd = mapForward.get(packet.dport);
2177-
if (fwd.ruid == packet.uid) {
2187+
if (packet.allowed)
2188+
if (mapForward.containsKey(packet.dport)) {
2189+
Forward fwd = mapForward.get(packet.dport);
2190+
if (fwd.ruid == packet.uid) {
2191+
allowed = new Allowed();
2192+
} else {
2193+
allowed = new Allowed(fwd.raddr, fwd.rport);
2194+
packet.data = "> " + fwd.raddr + "/" + fwd.rport;
2195+
}
2196+
} else
21782197
allowed = new Allowed();
2179-
} else {
2180-
allowed = new Allowed(fwd.raddr, fwd.rport);
2181-
packet.data = "> " + fwd.raddr + "/" + fwd.rport;
2182-
}
2183-
} else
2184-
allowed = new Allowed();
2185-
2186-
lock.readLock().unlock();
2198+
} finally {
2199+
lock.readLock().unlock();
2200+
}
21872201

21882202
if (prefs.getBoolean("log", false) || prefs.getBoolean("log_app", true))
21892203
if (packet.protocol != 6 /* TCP */ || !"".equals(packet.flags))
@@ -2347,8 +2361,11 @@ private boolean blockKnownTracker(String daddr, int uid) {
23472361
app = Common.getAppName(pm, uid);
23482362
uidToApp.put(uid, app);
23492363
}
2350-
assert tracker != null;
2351-
Log.i("TC-Log", app + " " + daddr + " " + ipToHost.get(daddr).getOrExpired() + " " + tracker.getName());
2364+
if (tracker != null) {
2365+
Expiring<String> host = ipToHost.get(daddr);
2366+
String hostName = (host != null) ? host.getOrExpired() : null;
2367+
Log.i("TC-Log", app + " " + daddr + " " + hostName + " " + tracker.getName());
2368+
}
23522369
} else {
23532370
if (tracker != NO_TRACKER) {
23542371
boolean blockedByGranularRule = false;

app/src/main/java/eu/faircode/netguard/Util.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,9 +586,10 @@ public static String getOrganization(String ip) throws Exception {
586586
return mapIPOrganization.get(ip);
587587
}
588588
BufferedReader reader = null;
589+
HttpURLConnection connection = null;
589590
try {
590591
URL url = new URL("https://ipinfo.io/" + ip + "/org");
591-
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
592+
connection = (HttpURLConnection) url.openConnection();
592593
connection.setRequestProperty("Accept-Encoding", "gzip");
593594
connection.setRequestMethod("GET");
594595
connection.setReadTimeout(15 * 1000);
@@ -607,6 +608,8 @@ public static String getOrganization(String ip) throws Exception {
607608
} finally {
608609
if (reader != null)
609610
reader.close();
611+
if (connection != null)
612+
connection.disconnect();
610613
}
611614
}
612615

app/src/main/java/net/kollnig/missioncontrol/Common.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,30 @@ object Common {
4343
*/
4444
@JvmStatic
4545
fun fetch(url: String?): String? {
46+
var conn: HttpURLConnection? = null
4647
try {
4748
val html = StringBuilder()
4849

49-
val conn = (URL(url)).openConnection() as HttpURLConnection
50+
conn = (URL(url)).openConnection() as HttpURLConnection
5051
conn.setRequestProperty("Accept-Encoding", "gzip")
5152
conn.setConnectTimeout(5000)
52-
val `in`: BufferedReader?
53-
if ("gzip" == conn.getContentEncoding()) `in` =
53+
val reader: BufferedReader = if ("gzip" == conn.getContentEncoding())
5454
BufferedReader(InputStreamReader(GZIPInputStream(conn.getInputStream())))
55-
else `in` = BufferedReader(InputStreamReader(conn.getInputStream()))
55+
else
56+
BufferedReader(InputStreamReader(conn.getInputStream()))
5657

57-
var str: String?
58-
while ((`in`.readLine().also { str = it }) != null) html.append(str)
59-
60-
`in`.close()
58+
reader.use { r ->
59+
var str: String?
60+
while ((r.readLine().also { str = it }) != null) html.append(str)
61+
}
6162

6263
return html.toString()
6364
} catch (e: IOException) {
6465
return null
6566
} catch (e: OutOfMemoryError) {
6667
return null
68+
} finally {
69+
conn?.disconnect()
6770
}
6871
}
6972

app/src/main/java/net/kollnig/missioncontrol/details/CountriesFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ private void showCountriesMap(ProgressBar pbLoading, ImageView mv, TextView txtF
163163
String countries = TextUtils.join(",#", hostCountriesCount.keySet());
164164
renderOptions.css(String.format("#%s { fill: #B71C1C; }", countries.toUpperCase()));
165165

166+
Picture picture = svg.renderToPicture(renderOptions);
166167
mv.post(() -> {
167-
Picture picture = svg.renderToPicture(renderOptions);
168168
mv.setImageDrawable(new PictureDrawable(picture));
169169
pbLoading.setVisibility(View.GONE);
170170
});

0 commit comments

Comments
 (0)