1+ // SPDX-License-Identifier: GPL-3.0-or-later
2+
3+ #include <stdio.h>
4+ #include <stdlib.h>
5+ #include <string.h>
6+ #include <unistd.h>
7+ #include <sys/stat.h>
8+ #include <sys/types.h>
9+ #include <fcntl.h>
10+ #include <errno.h>
11+
12+ #define SERVER_NAME "am_local_server"
13+ #define JAR_MAIN_CLASS "io.github.muntashirakon.AppManager.server.ServerRunner"
14+ #define TMP_PATH "/data/local/tmp"
15+
16+ int is_safe_string (const char * str ) {
17+ if (!str || str [0 ] == '\0' ) {
18+ return 0 ;
19+ }
20+
21+ // Prevent exactly "." or ".."
22+ if (strcmp (str , "." ) == 0 || strcmp (str , ".." ) == 0 ) {
23+ return 0 ;
24+ }
25+
26+ // Only allow alphanumeric, dot, underscore, and dash
27+ for (int i = 0 ; str [i ] != '\0' ; i ++ ) {
28+ char c = str [i ];
29+ if (!((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) ||
30+ (c >= '0' && c <= '9' ) || c == '.' || c == '_' || c == '-' )) {
31+ // Invalid character found
32+ return 0 ;
33+ }
34+ }
35+ // All characters are valid
36+ return 1 ;
37+ }
38+
39+ int copy_file (const char * src , const char * dst ) {
40+ int fd_src = open (src , O_RDONLY | O_CLOEXEC );
41+ if (fd_src < 0 ) {
42+ return -1 ;
43+ }
44+
45+ // Remove any existing file at destination to prevent symlink following
46+ unlink (dst );
47+
48+ int fd_dst = open (dst , O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC , 0755 );
49+ if (fd_dst < 0 ) {
50+ close (fd_src );
51+ return -1 ;
52+ }
53+
54+ char buf [BUFSIZ ];
55+ ssize_t n ;
56+ while ((n = read (fd_src , buf , sizeof (buf ))) > 0 ) {
57+ if (write (fd_dst , buf , n ) != n ) {
58+ // Write failed
59+ close (fd_src );
60+ close (fd_dst );
61+ unlink (dst );
62+ return -1 ;
63+ }
64+ }
65+
66+ close (fd_src );
67+ close (fd_dst );
68+
69+ return (n < 0 ) ? -1 : 0 ;
70+ }
71+
72+ int main (int argc , char * argv []) {
73+ if (argc < 8 ) {
74+ fprintf (stderr ,
75+ "USAGE: %s <port> <token> <jar_name> <AppID> <user_id> <bgrun(1|0)> <debug(1|0)> [extra_args...]\n" ,
76+ argv [0 ]);
77+ return 1 ;
78+ }
79+
80+ const char * port = argv [1 ];
81+ const char * token = argv [2 ];
82+ const char * jar_name = argv [3 ];
83+ const char * app_id = argv [4 ];
84+ const char * user_id = argv [5 ];
85+ const char * bgrun = argv [6 ];
86+ const char * debug = argv [7 ];
87+
88+ // Validate Paths
89+ if (!is_safe_string (jar_name ) || !is_safe_string (app_id ) || !is_safe_string (user_id )) {
90+ fprintf (stderr , "Error! Invalid characters in arguments.\n" );
91+ return 1 ;
92+ }
93+
94+ // Validate debug and bgrun
95+ if ((strcmp (bgrun , "0" ) != 0 && strcmp (bgrun , "1" ) != 0 ) ||
96+ (strcmp (debug , "0" ) != 0 && strcmp (debug , "1" ) != 0 )) {
97+ fprintf (stderr , "Error! bgrun and debug must be either 0 or 1.\n" );
98+ return 1 ;
99+ }
100+
101+ // /data/local/tmp/am.jar
102+ char exec_jar_path [512 ];
103+ if (snprintf (exec_jar_path , sizeof (exec_jar_path ), "%s/%s" , TMP_PATH , jar_name ) >=
104+ sizeof (exec_jar_path )) {
105+ fprintf (stderr , "Error! Buffer overflow on exec_jar_path.\n" );
106+ return 1 ;
107+ }
108+
109+ // Prioritized list of fallback source paths
110+ char fallbacks [4 ][512 ];
111+ snprintf (fallbacks [0 ], sizeof (fallbacks [0 ]), "/sdcard/Android/data/%s/cache/%s" , app_id ,
112+ jar_name );
113+ snprintf (fallbacks [1 ], sizeof (fallbacks [1 ]), "/storage/emulated/%s/Android/data/%s/cache/%s" ,
114+ user_id , app_id , jar_name );
115+ snprintf (fallbacks [2 ], sizeof (fallbacks [2 ]), "/sdcard/AppManager/%s" , jar_name );
116+ snprintf (fallbacks [3 ], sizeof (fallbacks [3 ]), "/storage/emulated/%s/AppManager/%s" , user_id ,
117+ jar_name );
118+
119+ uid_t uid = getuid ();
120+ gid_t gid = getgid ();
121+ printf ("Starting %s as %d:%d...\n" , SERVER_NAME , uid , gid );
122+
123+ // Copy am.jar as /data/local/tmp/am.jar
124+ const char * resolved_jar_path = NULL ;
125+ for (int i = 0 ; i < 4 ; i ++ ) {
126+ if (copy_file (fallbacks [i ], exec_jar_path ) == 0 ) {
127+ resolved_jar_path = fallbacks [i ];
128+ break ;
129+ }
130+ }
131+
132+ if (resolved_jar_path == NULL ) {
133+ fprintf (stderr , "Error! %s could not be found or copied.\n" , jar_name );
134+ return 1 ;
135+ }
136+
137+ // Fix ownership
138+ if (chown (exec_jar_path , uid , gid ) != 0 ) {
139+ fprintf (stderr , "Warning: chown failed: %s\n" , strerror (errno ));
140+ // Although it failed, still proceed
141+ }
142+
143+ // Build argument for am.jar
144+ char args_buf [2048 ];
145+ if (snprintf (args_buf , sizeof (args_buf ), "path:%s,token:%s,app:%s,bgrun:%s,debug:%s" ,
146+ port , token , app_id , bgrun , debug ) >= sizeof (args_buf )) {
147+ fprintf (stderr , "Error! Buffer overflow on args_buf.\n" );
148+ unlink (exec_jar_path );
149+ return 1 ;
150+ }
151+
152+ printf ("Resolved Jar path: %s\n" , resolved_jar_path );
153+ printf ("Args: %s\n" , args_buf );
154+
155+ // Execute app_process
156+ if (setenv ("CLASSPATH" , exec_jar_path , 1 ) != 0 ) {
157+ fprintf (stderr , "Error setting CLASSPATH\n" );
158+ unlink (exec_jar_path );
159+ return 1 ;
160+ }
161+
162+ int extra_args_count = argc - 8 ;
163+ int exec_argc = 6 + extra_args_count ;
164+ char * * exec_argv = malloc (sizeof (char * ) * exec_argc );
165+ if (!exec_argv ) {
166+ fprintf (stderr , "Out of memory\n" );
167+ unlink (exec_jar_path );
168+ return 1 ;
169+ }
170+
171+ exec_argv [0 ] = "app_process" ;
172+ exec_argv [1 ] = "/system/bin" ;
173+
174+ char nice_name_buf [128 ];
175+ snprintf (nice_name_buf , sizeof (nice_name_buf ), "--nice-name=%s" , SERVER_NAME );
176+ exec_argv [2 ] = nice_name_buf ;
177+
178+ exec_argv [3 ] = JAR_MAIN_CLASS ;
179+ exec_argv [4 ] = args_buf ;
180+
181+ for (int i = 0 ; i < extra_args_count ; i ++ ) {
182+ exec_argv [5 + i ] = argv [8 + i ];
183+ }
184+ exec_argv [exec_argc - 1 ] = NULL ;
185+
186+ printf ("Local server has started.\n" );
187+ fflush (stdout );
188+
189+ // Start in the background
190+ pid_t pid = fork ();
191+
192+ if (pid < 0 ) {
193+ fprintf (stderr , "Error! Fork failed: %s\n" , strerror (errno ));
194+ unlink (exec_jar_path );
195+ free (exec_argv );
196+ return 1 ;
197+ }
198+
199+ if (pid > 0 ) {
200+ // Parent process exits successfully
201+ printf ("Local server started successfully (PID: %d).\n" , pid );
202+ free (exec_argv );
203+ return 0 ;
204+ }
205+
206+ // Detach the background process
207+ if (setsid () < 0 ) {
208+ unlink (exec_jar_path );
209+ exit (1 );
210+ }
211+
212+ // Execute local server
213+ execv ("/system/bin/app_process" , exec_argv );
214+
215+ // If execv returns, an error occurred
216+ fprintf (stderr , "Error! Could not start local server: %s\n" , strerror (errno ));
217+
218+ // Cleanup
219+ unlink (exec_jar_path );
220+ free (exec_argv );
221+ exit (1 );
222+ }
0 commit comments