Add INTERACT_ACROSS_USERS permission

This is a signature-level permission required in order to call
getPackagesForUid for other users/profiles. It will be granted on first
root capture start.

See #217
This commit is contained in:
emanuele-f 2022-06-06 14:54:23 +02:00
parent e6bc27b3e3
commit fec54e9499
9 changed files with 120 additions and 90 deletions

View File

@ -11,9 +11,11 @@
<uses-permission android:name="android.permission.WRITE_CLIPS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.pcapdroid.permission.MITM" android:required="false"/>
<!-- Required with root to properly resolve UIDs cross-users/profiles. Granted via root -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" android:protectionLevel="signature" tools:ignore="ProtectedPermissions" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" />

View File

@ -112,7 +112,8 @@ public class AppsResolver {
try {
packages = mPm.getPackagesForUid(uid);
} catch (SecurityException e) {
// this is a bug in some devices https://github.com/AdguardTeam/AdguardForAndroid/issues/173
// A SecurityException is normally raised when trying to query a package of another user/profile
// without holding the INTERACT_ACROSS_USERS/INTERACT_ACROSS_PROFILES permissions
e.printStackTrace();
}

View File

@ -418,6 +418,12 @@ public class CaptureService extends VpnService implements Runnable {
Utils.showToast(this, R.string.vpn_setup_failed);
return abortStart();
}
} else {
// Root capture
if(checkCallingOrSelfPermission(Utils.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
boolean success = Utils.rootGrantPermission(Utils.INTERACT_ACROSS_USERS);
Utils.showToast(this, success ? R.string.permission_granted : R.string.permission_grant_fail, "INTERACT_ACROSS_USERS");
}
}
mWhitelist = PCAPdroid.getInstance().getMalwareWhitelist();
@ -1224,4 +1230,5 @@ public class CaptureService extends VpnService implements Runnable {
public static native void nativeSetFirewallEnabled(boolean enabled);
public static native int getNumCheckedMalwareConnections();
public static native int getNumCheckedFirewallConnections();
public static native int rootCmd(String prog, String args);
}

View File

@ -127,6 +127,7 @@ import javax.net.ssl.HttpsURLConnection;
public class Utils {
static final String TAG = "Utils";
public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
public static final int UID_UNKNOWN = -1;
public static final int UID_NO_FILTER = -2;
private static Boolean rootAvailable = null;
@ -502,13 +503,13 @@ public class Utils {
return false;
}
public static void showToast(Context context, int id) {
String msg = context.getResources().getString(id);
public static void showToast(Context context, int id, Object... args) {
String msg = context.getResources().getString(id, (Object[]) args);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
public static void showToastLong(Context context, int id) {
String msg = context.getResources().getString(id);
public static void showToastLong(Context context, int id, Object... args) {
String msg = context.getResources().getString(id, (Object[]) args);
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}
@ -1182,4 +1183,8 @@ public class Utils {
tv.setText(text);
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
public static boolean rootGrantPermission(String perm) {
return CaptureService.rootCmd("pm", String.format("grant %s %s", BuildConfig.APPLICATION_ID, perm)) == 0;
}
}

View File

@ -21,6 +21,8 @@
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <paths.h>
#include "utils.h"
memtrack_t memtrack = {0};
@ -28,6 +30,9 @@ int loglevel = 0;
const char *logtag = "pcapdroid-native";
void (*logcallback)(int lvl, const char *msg) = NULL;
// Needed for local compilation, don't remove
extern char **environ;
/* ******************************************************* */
void set_log_level(int lvl) {
@ -191,3 +196,78 @@ void hexdump(const char *buf, size_t bufsize) {
out[idx] = '\0';
log_d("%s", out);
}
/* ******************************************************* */
int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
int in_p[2], out_p[2];
int rv = -1;
pid_t pid;
if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
log_f("pipe failed[%d]: %s", errno, strerror(errno));
return -1;
}
if((pid = fork()) == 0) {
// child
char *argp[] = {"sh", "-c", as_root ? "su" : "sh", NULL};
close(in_p[1]);
close(out_p[0]);
dup2(in_p[0], STDIN_FILENO);
dup2(out_p[1], STDOUT_FILENO);
dup2(out_p[1], STDERR_FILENO);
execve(_PATH_BSHELL, argp, environ);
fprintf(stderr, "execve failed[%d]: %s", errno, strerror(errno));
exit(1);
} else if(pid > 0) {
// parent
int out = out_p[0];
close(in_p[0]);
close(out_p[1]);
// write "su" command input
log_d("run_shell_cmd[%d]: %s %s", pid, prog, args);
write(in_p[1], prog, strlen(prog));
write(in_p[1], " ", 1);
write(in_p[1], args, strlen(args));
write(in_p[1], "\n", 1);
close(in_p[1]);
waitpid(pid, &rv, 0);
if(check_error && (rv != 0)) {
char buf[128];
struct timeval timeout = {0};
fd_set fds;
buf[0] = '\0';
FD_ZERO(&fds);
FD_SET(out, &fds);
select(out + 1, &fds, NULL, NULL, &timeout);
if (FD_ISSET(out, &fds)) {
int num = read(out, buf, sizeof(buf) - 1);
if (num > 0)
buf[num] = '\0';
}
log_f("su \"%s\" invocation failed: %s", prog, buf);
rv = -1;
}
close(out_p[0]);
} else {
log_f("fork() failed[%d]: %s", errno, strerror(errno));
close(in_p[0]);
close(in_p[1]);
close(out_p[0]);
close(out_p[1]);
return -1;
}
return rv;
}

View File

@ -62,5 +62,6 @@ void tupleSwapPeers(zdtun_5tuple_t *tuple);
char loglvl2char(int lvl);
char* humanSize(char *buf, int bufsize, double bytes);
void hexdump(const char *buf, size_t bufsize);
int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error);
#endif // __LOG_UTILS_H__

View File

@ -19,16 +19,11 @@
#include <sys/un.h>
#include <linux/limits.h>
#include <sys/wait.h>
#include <paths.h>
#include "pcapdroid.h"
#include "pcapd/pcapd.h"
#include "common/utils.h"
#include "third_party/uthash.h"
// Needed for local compilation, don't remove
extern char **environ;
#ifdef FUZZING
extern int openPcap(pcapdroid_t *pd);
extern int nextPacket(pcapdroid_t *pd, pcapd_hdr_t *hdr, char *buf, size_t bufsize);
@ -50,81 +45,6 @@ typedef struct pcap_conn_t {
/* ******************************************************* */
static int run_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
int in_p[2], out_p[2];
int rv = -1;
pid_t pid;
if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
log_f("pipe failed[%d]: %s", errno, strerror(errno));
return -1;
}
if((pid = fork()) == 0) {
// child
char *argp[] = {"sh", "-c", as_root ? "su" : "sh", NULL};
close(in_p[1]);
close(out_p[0]);
dup2(in_p[0], STDIN_FILENO);
dup2(out_p[1], STDOUT_FILENO);
dup2(out_p[1], STDERR_FILENO);
execve(_PATH_BSHELL, argp, environ);
fprintf(stderr, "execve failed[%d]: %s", errno, strerror(errno));
exit(1);
} else if(pid > 0) {
// parent
int out = out_p[0];
close(in_p[0]);
close(out_p[1]);
// write "su" command input
log_d("run_cmd[%d]: %s %s", pid, prog, args);
write(in_p[1], prog, strlen(prog));
write(in_p[1], " ", 1);
write(in_p[1], args, strlen(args));
write(in_p[1], "\n", 1);
close(in_p[1]);
waitpid(pid, &rv, 0);
if(check_error && (rv != 0)) {
char buf[128];
struct timeval timeout = {0};
fd_set fds;
buf[0] = '\0';
FD_ZERO(&fds);
FD_SET(out, &fds);
select(out + 1, &fds, NULL, NULL, &timeout);
if (FD_ISSET(out, &fds)) {
int num = read(out, buf, sizeof(buf) - 1);
if (num > 0)
buf[num] = '\0';
}
log_f("su \"%s\" invocation failed: %s", prog, buf);
rv = -1;
}
close(out_p[0]);
} else {
log_f("fork() failed[%d]: %s", errno, strerror(errno));
close(in_p[0]);
close(in_p[1]);
close(out_p[0]);
close(out_p[1]);
return -1;
}
return rv;
}
/* ******************************************************* */
static void kill_pcapd(pcapdroid_t *nc) {
int pid;
char pid_s[8];
@ -138,7 +58,7 @@ static void kill_pcapd(pcapdroid_t *nc) {
if(pid != 0) {
log_d("Killing old pcapd with pid %d", pid);
run_cmd("kill", pid_s, true, false);
run_shell_cmd("kill", pid_s, true, false);
}
fclose(f);
@ -244,7 +164,7 @@ static int connectPcapd(pcapdroid_t *pd) {
// Start the daemon
char args[256];
snprintf(args, sizeof(args), "-l pcapd.log -i '%s' -d -u %d -t -b '%s'", pd->root.capture_interface, pd->app_filter, bpf);
if(run_cmd(pcapd, args, pd->root.as_root, true) != 0)
if(run_shell_cmd(pcapd, args, pd->root.as_root, true) != 0)
goto cleanup;
// Wait for pcapd to start

View File

@ -793,6 +793,19 @@ Java_com_emanuelef_remote_1capture_CaptureService_nativeSetFirewallEnabled(JNIEn
/* ******************************************************* */
JNIEXPORT int JNICALL
Java_com_emanuelef_remote_1capture_CaptureService_rootCmd(JNIEnv *env, jclass clazz, jstring prog,
jstring args) {
const char *prog_s = (*env)->GetStringUTFChars(env, prog, 0);
const char *args_s = (*env)->GetStringUTFChars(env, args, 0);
int rv = run_shell_cmd(prog_s, args_s, true, true);
(*env)->ReleaseStringUTFChars(env, prog, prog_s);
(*env)->ReleaseStringUTFChars(env, args, args_s);
return rv;
}
char* getStringPref(pcapdroid_t *pd, const char *key, char *buf, int bufsize) {
JNIEnv *env = pd->env;
@ -904,5 +917,4 @@ void getApplicationByUid(pcapdroid_t *pd, jint uid, char *buf, int bufsize) {
if(obj) (*env)->DeleteLocalRef(env, obj);
}
#endif // ANDROID
#endif // ANDROID

View File

@ -359,4 +359,6 @@
<string name="app_intro_firewall_msg">With the integrated <a href='%1$s'>Firewall</a> you can easily block Internet access to individual apps and domains\n\nCombine this with the built-in traffic visibility to get the ultimate tool to protect your privacy</string>
<string name="app_intro_malware_detection">Enhance the security of your device with the <a href='%1$s'>malware detection</a> feature\n\nBy using up-to-date blacklists, it can detect, block and alert malicious connections in real-time</string>
<string name="app_intro_traffic_dump">PCAPdroid provides <a href='%1$s'>multiple ways</a> to dump the traffic in the standard PCAP format for further analysis\n\nVia the <a href='%2$s'>trailer option</a>, you can add app names to the packets and display them in Wireshark</string>
<string name="permission_granted">%1$s permission was granted</string>
<string name="permission_grant_fail">%1$s permission could not be granted</string>
</resources>