Skip to content

Commit fb953b8

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 3bd977c commit fb953b8

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
@@ -1689,12 +1689,15 @@ private void startNative(final ParcelFileDescriptor vpn, List<Rule> listAllowed,
16891689
prepareForwarding();
16901690
} else {
16911691
lock.writeLock().lock();
1692-
mapUidAllowed.clear();
1693-
mapUidKnown.clear();
1694-
mapHostsBlocked.clear();
1695-
mapUidIPFilters.clear();
1696-
mapForward.clear();
1697-
lock.writeLock().unlock();
1692+
try {
1693+
mapUidAllowed.clear();
1694+
mapUidKnown.clear();
1695+
mapHostsBlocked.clear();
1696+
mapUidIPFilters.clear();
1697+
mapForward.clear();
1698+
} finally {
1699+
lock.writeLock().unlock();
1700+
}
16981701
}
16991702

17001703
if (log || log_app || filter) {
@@ -1760,26 +1763,30 @@ private void stopNative(ParcelFileDescriptor vpn) {
17601763

17611764
private void unprepare() {
17621765
lock.writeLock().lock();
1763-
mapUidAllowed.clear();
1764-
mapUidKnown.clear();
1765-
mapHostsBlocked.clear();
1766-
mapUidIPFilters.clear();
1767-
mapForward.clear();
1768-
lock.writeLock().unlock();
1766+
try {
1767+
mapUidAllowed.clear();
1768+
mapUidKnown.clear();
1769+
mapHostsBlocked.clear();
1770+
mapUidIPFilters.clear();
1771+
mapForward.clear();
1772+
} finally {
1773+
lock.writeLock().unlock();
1774+
}
17691775
}
17701776

17711777
private void prepareUidAllowed(List<Rule> listAllowed, List<Rule> listRule) {
17721778
lock.writeLock().lock();
1779+
try {
1780+
mapUidAllowed.clear();
1781+
for (Rule rule : listAllowed)
1782+
mapUidAllowed.put(rule.uid, true);
17731783

1774-
mapUidAllowed.clear();
1775-
for (Rule rule : listAllowed)
1776-
mapUidAllowed.put(rule.uid, true);
1777-
1778-
mapUidKnown.clear();
1779-
for (Rule rule : listRule)
1780-
mapUidKnown.put(rule.uid, rule.uid);
1781-
1782-
lock.writeLock().unlock();
1784+
mapUidKnown.clear();
1785+
for (Rule rule : listRule)
1786+
mapUidKnown.put(rule.uid, rule.uid);
1787+
} finally {
1788+
lock.writeLock().unlock();
1789+
}
17831790
}
17841791

17851792
public static void prepareHostsBlocked(Context c) {
@@ -1805,27 +1812,31 @@ public static void prepareHostsBlocked(Context c) {
18051812
}
18061813

18071814
lock.writeLock().lock();
1808-
mapHostsBlocked.clear();
1815+
try {
1816+
mapHostsBlocked.clear();
18091817

1810-
int count = 0;
1811-
br = new BufferedReader(is);
1812-
String line;
1813-
while ((line = br.readLine()) != null) {
1814-
int hash = line.indexOf('#');
1815-
if (hash >= 0)
1816-
line = line.substring(0, hash);
1817-
line = line.trim();
1818-
if (line.length() > 0) {
1819-
String[] words = line.split("\\s+");
1820-
if (words.length == 2) {
1821-
count++;
1822-
mapHostsBlocked.put(words[1], true);
1823-
} else
1824-
Log.i(TAG, "Invalid hosts file line: " + line);
1825-
}
1826-
}
1827-
mapHostsBlocked.put("test.netguard.me", true);
1828-
Log.i(TAG, count + " hosts read");
1818+
int count = 0;
1819+
br = new BufferedReader(is);
1820+
String line;
1821+
while ((line = br.readLine()) != null) {
1822+
int hash = line.indexOf('#');
1823+
if (hash >= 0)
1824+
line = line.substring(0, hash);
1825+
line = line.trim();
1826+
if (line.length() > 0) {
1827+
String[] words = line.split("\\s+");
1828+
if (words.length == 2) {
1829+
count++;
1830+
mapHostsBlocked.put(words[1], true);
1831+
} else
1832+
Log.i(TAG, "Invalid hosts file line: " + line);
1833+
}
1834+
}
1835+
mapHostsBlocked.put("test.netguard.me", true);
1836+
Log.i(TAG, count + " hosts read");
1837+
} finally {
1838+
lock.writeLock().unlock();
1839+
}
18291840
} catch (IOException ex) {
18301841
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
18311842
} finally {
@@ -1843,8 +1854,6 @@ public static void prepareHostsBlocked(Context c) {
18431854
}
18441855
}
18451856

1846-
lock.writeLock().unlock();
1847-
18481857
// Reload TrackerList to ensure it stays in sync with updated hosts
18491858
TrackerList.reloadTrackerData(c);
18501859
}
@@ -1853,11 +1862,11 @@ private void prepareUidIPFilters(String dname) {
18531862
SharedPreferences lockdown = getSharedPreferences("lockdown", Context.MODE_PRIVATE);
18541863

18551864
lock.writeLock().lock();
1865+
try {
1866+
if (dname == null) // reset mechanism, called from startNative()
1867+
mapUidIPFilters.clear();
18561868

1857-
if (dname == null) // reset mechanism, called from startNative()
1858-
mapUidIPFilters.clear();
1859-
1860-
try (Cursor cursor = DatabaseHelper.getInstance(ServiceSinkhole.this).getAccessDns(dname)) {
1869+
try (Cursor cursor = DatabaseHelper.getInstance(ServiceSinkhole.this).getAccessDns(dname)) {
18611870
int colUid = cursor.getColumnIndex("uid");
18621871
int colVersion = cursor.getColumnIndex("version");
18631872
int colProtocol = cursor.getColumnIndex("protocol");
@@ -1930,13 +1939,15 @@ private void prepareUidIPFilters(String dname) {
19301939
}
19311940
}
19321941
}
1933-
1934-
lock.writeLock().unlock();
1942+
} finally {
1943+
lock.writeLock().unlock();
1944+
}
19351945
}
19361946

19371947
private void prepareForwarding() {
19381948
lock.writeLock().lock();
1939-
mapForward.clear();
1949+
try {
1950+
mapForward.clear();
19401951

19411952
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
19421953
if (prefs.getBoolean("filter", true)) {
@@ -1983,7 +1994,9 @@ private void prepareForwarding() {
19831994
Log.i(TAG, "DoH Forward " + dnsFwd);
19841995
}
19851996
}
1986-
lock.writeLock().unlock();
1997+
} finally {
1998+
lock.writeLock().unlock();
1999+
}
19872000
}
19882001

19892002
private boolean isLockedDown(boolean metered) {
@@ -2158,68 +2171,69 @@ public static void clearTrackerCaches() {
21582171
private Allowed isAddressAllowed(Packet packet) {
21592172
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
21602173

2174+
Allowed allowed = null;
21612175
lock.readLock().lock();
2176+
try {
2177+
packet.allowed = false;
2178+
if (prefs.getBoolean("filter", true)) {
2179+
// https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
2180+
if (packet.protocol == 17 /* UDP */ && !prefs.getBoolean("filter_udp", true)) {
2181+
// Allow unfiltered UDP
2182+
packet.allowed = true;
2183+
Log.i(TAG, "Allowing UDP " + packet);
2184+
} else if ((packet.uid < 2000) &&
2185+
!mapUidKnown.containsKey(packet.uid) && isSupported(packet.protocol)) {
2186+
// Allow unknown (system) traffic
2187+
packet.allowed = true;
2188+
Log.w(TAG, "Allowing unknown system " + packet);
2189+
} else if (packet.uid == Process.myUid()) {
2190+
// Allow self
2191+
packet.allowed = true;
2192+
Log.w(TAG, "Allowing self " + packet);
2193+
} else {
2194+
boolean filtered = false;
21622195

2163-
packet.allowed = false;
2164-
if (prefs.getBoolean("filter", true)) {
2165-
// https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
2166-
if (packet.protocol == 17 /* UDP */ && !prefs.getBoolean("filter_udp", true)) {
2167-
// Allow unfiltered UDP
2168-
packet.allowed = true;
2169-
Log.i(TAG, "Allowing UDP " + packet);
2170-
} else if ((packet.uid < 2000) &&
2171-
!mapUidKnown.containsKey(packet.uid) && isSupported(packet.protocol)) {
2172-
// Allow unknown (system) traffic
2173-
packet.allowed = true;
2174-
Log.w(TAG, "Allowing unknown system " + packet);
2175-
} else if (packet.uid == Process.myUid()) {
2176-
// Allow self
2177-
packet.allowed = true;
2178-
Log.w(TAG, "Allowing self " + packet);
2179-
} else {
2180-
boolean filtered = false;
2196+
if (packet.data != null && !packet.data.isEmpty())
2197+
Log.d(TAG, "Found SNI in isAddressAllowed: " + packet.data);
21812198

2182-
if (packet.data != null && !packet.data.isEmpty())
2183-
Log.d(TAG, "Found SNI in isAddressAllowed: " + packet.data);
2199+
// Check if tracker is known
2200+
// In minimal mode (including TC Slim), always enable blocking
2201+
if (blockKnownTracker(packet.daddr, packet.uid)) {
2202+
filtered = true;
2203+
packet.allowed = false;
2204+
}
21842205

2185-
// Check if tracker is known
2186-
// In minimal mode (including TC Slim), always enable blocking
2187-
if (blockKnownTracker(packet.daddr, packet.uid)) {
2188-
filtered = true;
2189-
packet.allowed = false;
2190-
}
2206+
InternetBlocklist internetBlocklist = InternetBlocklist.getInstance(ServiceSinkhole.this);
2207+
if (internetBlocklist.blockedInternet(packet.uid)) {
2208+
filtered = true;
2209+
packet.allowed = false;
2210+
}
21912211

2192-
InternetBlocklist internetBlocklist = InternetBlocklist.getInstance(ServiceSinkhole.this);
2193-
if (internetBlocklist.blockedInternet(packet.uid)) {
2194-
filtered = true;
2195-
packet.allowed = false;
2212+
if (!filtered)
2213+
packet.allowed = true;
21962214
}
2197-
2198-
if (!filtered)
2199-
packet.allowed = true;
22002215
}
2201-
}
22022216

2203-
// Block DNS-over-TLS (DoT) on port 853 to prevent bypassing DNS filtering
2204-
if (packet.allowed && packet.dport == 853 && prefs.getBoolean("block_dot", true)) {
2205-
Log.i(TAG, "Blocking DoT " + packet);
2206-
packet.allowed = false;
2207-
}
2217+
// Block DNS-over-TLS (DoT) on port 853 to prevent bypassing DNS filtering
2218+
if (packet.allowed && packet.dport == 853 && prefs.getBoolean("block_dot", true)) {
2219+
Log.i(TAG, "Blocking DoT " + packet);
2220+
packet.allowed = false;
2221+
}
22082222

2209-
Allowed allowed = null;
2210-
if (packet.allowed)
2211-
if (mapForward.containsKey(packet.dport)) {
2212-
Forward fwd = mapForward.get(packet.dport);
2213-
if (fwd.ruid == packet.uid) {
2223+
if (packet.allowed)
2224+
if (mapForward.containsKey(packet.dport)) {
2225+
Forward fwd = mapForward.get(packet.dport);
2226+
if (fwd.ruid == packet.uid) {
2227+
allowed = new Allowed();
2228+
} else {
2229+
allowed = new Allowed(fwd.raddr, fwd.rport);
2230+
packet.data = "> " + fwd.raddr + "/" + fwd.rport;
2231+
}
2232+
} else
22142233
allowed = new Allowed();
2215-
} else {
2216-
allowed = new Allowed(fwd.raddr, fwd.rport);
2217-
packet.data = "> " + fwd.raddr + "/" + fwd.rport;
2218-
}
2219-
} else
2220-
allowed = new Allowed();
2221-
2222-
lock.readLock().unlock();
2234+
} finally {
2235+
lock.readLock().unlock();
2236+
}
22232237

22242238
if (prefs.getBoolean("log", false) || prefs.getBoolean("log_app", true))
22252239
if (packet.protocol != 6 /* TCP */ || !"".equals(packet.flags))
@@ -2379,8 +2393,11 @@ private boolean blockKnownTracker(String daddr, int uid) {
23792393
app = Common.getAppName(pm, uid);
23802394
uidToApp.put(uid, app);
23812395
}
2382-
assert tracker != null;
2383-
Log.i("TC-Log", app + " " + daddr + " " + ipToHost.get(daddr).getOrExpired() + " " + tracker.getName());
2396+
if (tracker != null) {
2397+
Expiring<String> host = ipToHost.get(daddr);
2398+
String hostName = (host != null) ? host.getOrExpired() : null;
2399+
Log.i("TC-Log", app + " " + daddr + " " + hostName + " " + tracker.getName());
2400+
}
23842401
} else {
23852402
if (tracker != NO_TRACKER) {
23862403
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)