Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5fbd0cf
Fix profile-only app discovery
Mainkill1 Jun 26, 2026
419c2fe
Allow loopback outside LAN filtering
Mainkill1 Jun 26, 2026
939cf74
Log rejects from all firewall chains
Mainkill1 Jun 26, 2026
28f1b35
Wait for IPv6 apply before completion
Mainkill1 Jun 26, 2026
726a9cd
Avoid static widget view references
Mainkill1 Jun 26, 2026
bcb3341
Add package name search and display option
Mainkill1 Jun 26, 2026
1fa83fb
Hide app list until launch security passes
Mainkill1 Jun 26, 2026
9195666
Skip boot rule apply when firewall is disabled
Mainkill1 Jun 26, 2026
360b811
Make app list UID sort deterministic
Mainkill1 Jun 26, 2026
9de40b9
Resolve hidden UIDs in firewall logs
Mainkill1 Jun 26, 2026
9805d4c
Allow LAN discovery destinations
Mainkill1 Jun 26, 2026
40446bf
Allow required IPv6 control traffic
Mainkill1 Jun 26, 2026
a67b225
Validate Tasker plugin profile ids
Mainkill1 Jun 26, 2026
3231d50
Allow DHCP replies from tether services
Mainkill1 Jun 26, 2026
e7db2a2
Recognize WireGuard and Bluetooth tether interfaces
Mainkill1 Jun 26, 2026
c79dd1c
Recover from stale fingerprint enrollment
Mainkill1 Jun 26, 2026
6d0cfa8
Route Tor redirects before network filters
Mainkill1 Jun 26, 2026
72b57b9
Avoid reusing widget callback messages
Mainkill1 Jun 26, 2026
40d68f6
Honor disable confirmation from widgets
Mainkill1 Jun 26, 2026
7a8f793
Refresh rules on VPN network changes
Mainkill1 Jun 26, 2026
8346953
Avoid Tor owner matches on inbound rules
Mainkill1 Jun 26, 2026
73e907c
Move tether DHCP helper away from IPv6 helper
Mainkill1 Jun 26, 2026
a34797e
Track VPN networks before loss refreshes
Mainkill1 Jun 26, 2026
dec5d57
Reduce profile discovery scan overhead
Mainkill1 Jun 26, 2026
b3646bf
Fix picker URI permission persistence
Mainkill1 Jun 26, 2026
df34e59
Merge picker URI permission fix
Mainkill1 Jun 26, 2026
1f5c115
Merge IPv6 rule apply completion fix
Mainkill1 Jun 26, 2026
c0917b0
Merge disabled boot apply fix
Mainkill1 Jun 26, 2026
af888d5
Merge fingerprint enrollment recovery fix
Mainkill1 Jun 26, 2026
fa198fe
Merge firewall reject logging fix
Mainkill1 Jun 26, 2026
f2854a9
Merge IPv6 control traffic fix
Mainkill1 Jun 26, 2026
37e6241
Merge launch security content gate fix
Mainkill1 Jun 26, 2026
a737bf9
Merge LAN discovery and loopback fix
Mainkill1 Jun 26, 2026
011e724
Merge package-name search and sorting fix
Mainkill1 Jun 26, 2026
df46224
Merge plugin profile id validation fix
Mainkill1 Jun 26, 2026
1933251
Merge secondary profile app discovery fix
Mainkill1 Jun 26, 2026
e081276
Merge tether DHCP reply fix
Mainkill1 Jun 26, 2026
c0b43b6
Merge Tor redirect ordering fix
Mainkill1 Jun 26, 2026
b7ac716
Merge VPN network refresh fix
Mainkill1 Jun 26, 2026
8df1902
Merge VPN and tether interface detection fix
Mainkill1 Jun 26, 2026
6beb10a
Merge widget disable confirmation fix
Mainkill1 Jun 26, 2026
5ff09fd
Merge widget callback message fix
Mainkill1 Jun 26, 2026
c425a23
Merge widget view reference fix
Mainkill1 Jun 26, 2026
b831ef8
Attach Bug-Attack APK builds
Mainkill1 Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
417 changes: 351 additions & 66 deletions app/src/main/java/dev/ukanth/ufirewall/Api.java

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions app/src/main/java/dev/ukanth/ufirewall/InterfaceTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ public final class InterfaceTracker {
"wwan+", "cdma_rmnet+", "clat4+", "cc2mni+", "bond1+", "rmnet_smux+", "ccinet+",
"v4-rmnet+", "seth_w+", "v4-rmnet_data+", "rmnet_ipa+", "rmnet_data+", "r_rmnet_data+"};

public static final String[] ITFS_VPN = {"tun+", "ppp+", "tap+"};
public static final String[] ITFS_VPN = {"tun+", "ppp+", "tap+", "wg+"};

public static final String[] ITFS_TETHER = {"bt-pan", "usb+", "rndis+", "rmnet_usb+"};
public static final String[] ITFS_TETHER = {"bt-pan", "bnep+", "usb+", "rndis+", "rmnet_usb+"};

public static final String BOOT_COMPLETED = "BOOT_COMPLETED";
public static final String CONNECTIVITY_CHANGE = "CONNECTIVITY_CHANGE";
Expand Down
88 changes: 79 additions & 9 deletions app/src/main/java/dev/ukanth/ufirewall/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
private TextWatcher filterTextWatcher;
private MaterialDialog runProgress;
private String currentSearchQuery = "";
private boolean launchSecurityPassed = false;

private BroadcastReceiver uiProgressReceiver4, uiProgressReceiver6, toastReceiver, themeRefreshReceiver, uiRefreshReceiver;
private IntentFilter uiFilter4, uiFilter6;
Expand Down Expand Up @@ -805,6 +806,12 @@ private void reloadPreferences() {
setupMultiProfile();
}

if (isLaunchSecurityLocked()) {
concealProtectedContent();
return;
}
revealProtectedContent();

// Use async loading to avoid blocking the main thread
// If the app list is already cached, filterApps will use the cache (fast path)
// If not cached, showOrLoadApplications will load asynchronously with a progress dialog
Expand Down Expand Up @@ -1118,6 +1125,10 @@ private void showApplications(final String searchStr) {
break;
}
}
// Package names are stored separately from labels, so include them in search.
if (!matches && app.pkgName != null && app.pkgName.toLowerCase().contains(normalizedSearch)) {
matches = true;
}
if (!matches && G.showUid()) {
matches = String.valueOf(app.uid).contains(normalizedSearch);
}
Expand Down Expand Up @@ -1679,14 +1690,22 @@ private void takePersistablePermission(Intent data, int permissionFlag) {
if (uri == null) {
return;
}
int flags = data.getFlags() & permissionFlag;
if (flags == 0) {
return;
int grantedFlags = data.getFlags();
if ((permissionFlag & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
&& (grantedFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
try {
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.w(TAG, "Unable to persist picker read URI permission", e);
}
}
try {
getContentResolver().takePersistableUriPermission(uri, flags);
} catch (SecurityException e) {
Log.w(TAG, "Unable to persist picker URI permission", e);
if ((permissionFlag & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
&& (grantedFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
try {
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} catch (SecurityException e) {
Log.w(TAG, "Unable to persist picker write URI permission", e);
}
}
}

Expand Down Expand Up @@ -1786,7 +1805,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
case REQ_ENTER_PATTERN: {
switch (resultCode) {
case RESULT_OK:
showOrLoadApplications();
onLaunchSecurityPassed();
break;
default:
MainActivity.this.finish();
Expand Down Expand Up @@ -3097,10 +3116,61 @@ private void runLaunchSecurityCheck() {
Intent intent = getIntent();
if (intent != null && intent.getBooleanExtra(EXTRA_SKIP_SECURITY_CHECK_ON_RECREATE, false)) {
intent.removeExtra(EXTRA_SKIP_SECURITY_CHECK_ON_RECREATE);
launchSecurityPassed = true;
Log.i(Api.TAG, "Skipping security check for internal UI/theme refresh");
return;
}
new SecurityUtil(MainActivity.this).passCheck();
SecurityUtil securityUtil = new SecurityUtil(MainActivity.this, this::onLaunchSecurityPassed);
if (!securityUtil.isPasswordProtected()) {
onLaunchSecurityPassed();
return;
}
concealProtectedContent();
if (!securityUtil.passCheck()) {
onLaunchSecurityPassed();
}
}

private boolean isLaunchSecurityLocked() {
Intent intent = getIntent();
if (intent != null && intent.getBooleanExtra(EXTRA_SKIP_SECURITY_CHECK_ON_RECREATE, false)) {
return false;
}
return !launchSecurityPassed && new SecurityUtil(MainActivity.this).isPasswordProtected();
}

private void onLaunchSecurityPassed() {
launchSecurityPassed = true;
reloadPreferences();
}

private void concealProtectedContent() {
// Launch security must hide cached app rows until auth succeeds.
if (listview != null) {
listview.setAdapter(null);
listview.setVisibility(View.INVISIBLE);
}
if (mSwipeLayout != null) {
mSwipeLayout.setEnabled(false);
}
setOptionalViewVisibility(R.id.filerOption, View.INVISIBLE);
setOptionalViewVisibility(R.id.profileOption, View.INVISIBLE);
}

private void revealProtectedContent() {
if (listview != null) {
listview.setVisibility(View.VISIBLE);
}
if (mSwipeLayout != null) {
mSwipeLayout.setEnabled(true);
}
}

private void setOptionalViewVisibility(int id, int visibility) {
View view = findViewById(id);
if (view != null) {
view.setVisibility(visibility);
}
}

@RequiresApi(28)
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/dev/ukanth/ufirewall/log/LogInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,18 @@ public static LogInfo parseLogs(String result, final Context ctx, String pattern
//system level packages
try {
if (!appNameMap.containsKey(uid)) {
appName = ctx.getPackageManager().getNameForUid(uid);
appName = null;
for (PackageInfoData app : apps) {
if (app.uid == uid) {
appName = app.names.get(0);
break;
}
}
// Android package visibility can hide packages from PackageManager.
// Fall back to shell-backed UID resolution before showing "Deleted App".
if (appName == null || appName.length() == 0) {
appName = UidResolver.resolveUid(ctx, uid);
}
} else {
appName = appNameMap.get(uid);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,18 @@ public static boolean isBundleValid(final Bundle bundle)
return false;
}
/*
* Make sure the extra isn't null or empty
* Make sure the extra is a numeric action/profile id. Locale/Tasker callers can send
* arbitrary exported extras, so reject malformed values before parseInt() call sites.
*/
return !TextUtils.isEmpty(bundle.getString(BUNDLE_EXTRA_STRING_MESSAGE));
String message = bundle.getString(BUNDLE_EXTRA_STRING_MESSAGE);
if (TextUtils.isEmpty(message)) {
return false;
}
String index = message;
if (message.contains("::")) {
index = message.split("::", 2)[0];
}
return !TextUtils.isEmpty(index) && TextUtils.isDigitsOnly(index);
}

/**
Expand All @@ -89,4 +98,4 @@ private PluginBundleManager()
{
throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin
Context ctx = getApplicationContext();
boolean isRefreshRequired = false;

if (key.equals("showUid") || key.equals("disableIcons") || key.equals("enableVPN")
if (key.equals("showUid") || key.equals("showPackageName") || key.equals("disableIcons") || key.equals("enableVPN")
|| key.equals("enableTether")
|| key.equals("enableLAN") || key.equals("enableRoam")
|| key.equals("enableCustomRules")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ public void onInput(MaterialDialog dialog, CharSequence input) {
itemList.setValueIndex(3);
dialog.dismiss();
});
dialog.setOnFingerprintUnavailable(() -> {
itemList.setValueIndex(0);
});
dialog.setOnFingerprintSuccess(() -> {
G.isFingerprintEnabled(false);
Api.toast(context, getString(R.string.fingerprint_disabled_successfully));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,7 @@ public View getView(final int position, View convertView, ViewGroup parent) {

holder.app = listApps.get(position);

if (G.showUid()) {
holder.text.setText(holder.app.toStringWithUID());
} else {
holder.text.setText(holder.app.toString());
}
holder.text.setText(holder.app.toStringForList(G.showUid(), G.showPackageName()));

final int id = holder.app.uid;
final View finalConvertView = convertView;
Expand Down
19 changes: 18 additions & 1 deletion app/src/main/java/dev/ukanth/ufirewall/util/BootRuleManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ public class BootRuleManager {
public static void initializeBootRuleApplication(Context context) {
synchronized (ruleApplicationLock) {
Log.i(TAG, "Initializing boot rule application");

if (!shouldApplyBootRules(context)) {
Log.i(TAG, "Firewall disabled or inactive at boot; skipping boot rule application");
markBootComplete();
return;
}

// Mark boot as in progress
isBootInProgress.set(true);
Expand Down Expand Up @@ -114,6 +120,11 @@ private static void scheduleDelayedBootRules(Context context) {
Runnable delayedRules = () -> {
synchronized (ruleApplicationLock) {
if (isBootInProgress.get()) {
if (!shouldApplyBootRules(context)) {
Log.i(TAG, "Firewall disabled before delayed boot apply; skipping delayed rules");
markBootComplete();
return;
}
Log.i(TAG, "Applying delayed boot rules");
try {
// Force interface configuration refresh for delayed rules
Expand Down Expand Up @@ -147,6 +158,12 @@ private static void cancelDelayedBootRules() {
}
delayedBootRulesScheduled.set(false);
}

private static boolean shouldApplyBootRules(Context context) {
// BootRuleManager calls applyBootRules directly, so keep the same enabled checks
// that protect normal connectivity-change rule application.
return Api.isEnabled(context) && G.activeRules();
}

/**
* Mark boot process as complete
Expand Down Expand Up @@ -214,4 +231,4 @@ public static String getBootState() {
initialBootRulesApplied.get(),
delayedBootRulesScheduled.get());
}
}
}
44 changes: 40 additions & 4 deletions app/src/main/java/dev/ukanth/ufirewall/util/FingerprintUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public static class FingerprintDialog extends Dialog {
// callbacks
OnFingerprintFailure failureCallback;
OnFingerprintSuccess successCallback;
OnFingerprintUnavailable unavailableCallback;

@RequiresApi(api = Build.VERSION_CODES.M)
public FingerprintDialog(Context context) {
Expand Down Expand Up @@ -186,6 +187,10 @@ public void setOnFingerprintSuccess(OnFingerprintSuccess doSomething){
successCallback = doSomething;
}

public void setOnFingerprintUnavailable(OnFingerprintUnavailable mayHappen){
unavailableCallback = mayHappen;
}

/**
* Created by whit3hawks on 11/16/16.
* Modified by vzool on 1/14/17.
Expand All @@ -200,19 +205,23 @@ void startReadFingerTip(){
* because we already checked if device support fingerprint before enable it.
* We just leave it as-is for the days, who knows! :)
*/
errorText.setText(R.string.device_with_no_fingerprint_sensor);
handleUnavailableFingerprint(R.string.device_with_no_fingerprint_sensor,
"Fingerprint hardware is not available");
}else {
// Checks whether fingerprint permission is set on manifest
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
errorText.setText(R.string.fingerprint_permission_manifest_missing);
handleUnavailableFingerprint(R.string.fingerprint_permission_manifest_missing,
"Fingerprint permission is missing");
}else{
// Check whether at least one fingerprint is registered
if (!fingerprintManager.hasEnrolledFingerprints()) {
errorText.setText(R.string.register_at_least_one_fingerprint);
handleUnavailableFingerprint(R.string.register_at_least_one_fingerprint,
"No enrolled fingerprints are available");
}else{
// Checks whether lock screen security is enabled or not
if (!keyguardManager.isKeyguardSecure()) {
errorText.setText(R.string.lock_screen_not_enabled);
handleUnavailableFingerprint(R.string.lock_screen_not_enabled,
"Lock screen security is not enabled");
}else{

generateKey();
Expand All @@ -227,6 +236,9 @@ void startReadFingerTip(){
}

helper.startAuth(fingerprintManager, cryptoObject);
} else {
handleUnavailableFingerprint(R.string.fingerprint_security_changed,
"Fingerprint keystore key was invalidated");
}
}
}
Expand All @@ -252,6 +264,24 @@ private void triggerSuccess(){
}
}

private void handleUnavailableFingerprint(int messageResId, String reason) {
// Fingerprint enrollment or lock-screen changes invalidate the saved keystore key.
Log.e(TAG, reason);
G.isFingerprintEnabled(false);
if (errorText != null) {
errorText.setText(messageResId);
}
Api.toast(getContext(), getContext().getString(R.string.fingerprint_security_changed));
if(isShowing()) {
dismiss();
}
if(unavailableCallback != null){
unavailableCallback.then();
} else if(failureCallback != null){
failureCallback.then();
}
}

@TargetApi(Build.VERSION_CODES.M)
private void generateKey() {
try {
Expand Down Expand Up @@ -304,6 +334,7 @@ private boolean cipherInit() {
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
Log.e(TAG, "Fingerprint keystore key permanently invalidated", e);
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
Expand Down Expand Up @@ -394,4 +425,9 @@ public interface OnFingerprintFailure{
public interface OnFingerprintSuccess{
void then();
}

// interface for callback when fingerprint setup is no longer usable
public interface OnFingerprintUnavailable{
void then();
}
}
Loading