1 /*
2  * Copyright (C) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 #include <termios.h>
17 #include <cstring>
18 #include <unistd.h>
19 #include <climits>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <sys/stat.h>
23 #include <securec.h>
24 
25 #if defined(SURPPORT_SELINUX)
26 #include "selinux/selinux.h"
27 #endif
28 #include "account_iam_client.h"
29 #include "os_account_manager.h"
30 #include "sudo_iam.h"
31 
32 #define PWD_BUF_LEN 128
33 #define DEFAULT_PATH "/system/bin"
34 #define DEFAULT_BASH "/system/bin/sh"
35 
36 using namespace OHOS::UserIam;
37 using namespace OHOS::AccountSA;
38 
39 static FILE *g_ttyFp = nullptr;
40 
41 static const char *OUT_OF_MEM = "[E0001] out of memory\n";
42 static const char *COMMAND_NOT_FOUND = "[E0002] command not found\n";
43 static const char *USER_VERIFY_FAILED = "[E0003] Sorry, try again. If screen lock password not set, set it first.\n";
44 
WriteStdErr(const char *str)45 static void WriteStdErr(const char *str)
46 {
47     (void)fwrite(str, 1, strlen(str), stderr);
48     fflush(stderr);
49 }
50 
WriteTty(const char *str)51 static void WriteTty(const char *str)
52 {
53     if (g_ttyFp != nullptr) {
54         (void)fwrite(str, 1, strlen(str), g_ttyFp);
55         fflush(g_ttyFp);
56     } else {
57         g_ttyFp = fopen("/dev/tty", "w");
58         if (g_ttyFp != nullptr) {
59             (void)fwrite(str, 1, strlen(str), g_ttyFp);
60             fflush(g_ttyFp);
61             return;
62         }
63         WriteStdErr("open /dev/tty for write failed\n");
64     }
65 }
66 
CloseTty(void)67 static void CloseTty(void)
68 {
69     if (g_ttyFp != nullptr) {
70         fclose(g_ttyFp);
71     }
72     g_ttyFp = nullptr;
73 }
74 
StrDup(const char *str)75 static char *StrDup(const char *str)
76 {
77     int ret;
78     char *result = new(std::nothrow)char[strlen(str) + 1];
79     if (result == nullptr) {
80         WriteStdErr(OUT_OF_MEM);
81         exit(1);
82     }
83     ret = strcpy_s(result, strlen(str) + 1, str);
84     if (ret != 0) {
85         WriteStdErr(OUT_OF_MEM);
86         exit(1);
87     }
88     return result;
89 }
90 
FreeArgvNew(char **argvNew)91 static void FreeArgvNew(char **argvNew)
92 {
93     char **p = nullptr;
94     for (p = argvNew; *p != nullptr; p++) {
95         delete [] *p;
96     }
97     delete [] argvNew;
98 }
99 
100 /*
101  * Find cmd from PATH
102 */
GetCmdInPath(char *cmd, int cmdBufLen, char *envp[])103 static bool GetCmdInPath(char *cmd, int cmdBufLen, char *envp[])
104 {
105     struct stat st;
106     char *path = nullptr;
107     char *pathBak = nullptr;
108     char **ep = nullptr;
109     char *cp = nullptr;
110     char pathBuf[PATH_MAX + 1] = {0};
111     bool findSuccess = false;
112 
113     if (strchr(cmd, '/') != nullptr) {
114         return true;
115     }
116 
117     for (ep = envp; *ep != nullptr; ep++) {
118         if (strcmp(*ep, "PATH=") == 0) {
119             path = *ep + strlen("PATH=");
120             break;
121         }
122     }
123 
124     path = StrDup((path != nullptr && *path != '\0') ? path : DEFAULT_PATH);
125     pathBak = path;
126     do {
127         if ((cp = strchr(path, ':')) != nullptr) {
128             *cp = '\0';
129         }
130         int ret = sprintf_s(pathBuf, sizeof(pathBuf), "%s/%s", *path ? path : ".", cmd);
131         if (ret > 0 && stat(pathBuf, &st) == 0 && S_ISREG(st.st_mode)) {
132             findSuccess = true;
133             break;
134         }
135         path = cp + 1;
136     } while (cp != nullptr);
137 
138     free(pathBak);
139     if (!findSuccess) {
140         WriteTty(COMMAND_NOT_FOUND);
141         return false;
142     }
143     return (sprintf_s(cmd, cmdBufLen, "%s", pathBuf) < 0) ? false : true;
144 }
145 
ParseCmd(int argc, char* argv[], char* env[], char *cmd, int cmdLen)146 static char **ParseCmd(int argc, char* argv[], char* env[], char *cmd, int cmdLen)
147 {
148     int startCopyArgvIndex = 1;
149     int argvNewIndex = 0;
150     char **argvTmp = nullptr;
151     bool isShc = false;
152     int ret;
153 
154     /*
155      * Here, we construct the command and its argv
156      * sudo sh -c xxx yyy -----> sh -c xxx yyy
157      * sudo xxx yyy       -----> xxx yyy
158     */
159     if (argc <= 0) {
160         return nullptr;
161     }
162     argvTmp = new(std::nothrow) char* [argc];
163     if (argvTmp == nullptr) {
164         WriteStdErr(OUT_OF_MEM);
165         return nullptr;
166     }
167     (void)memset_s(argvTmp, sizeof(char*) * argc, 0, sizeof(char*) * argc);
168     /*
169      * sudo sh -c xxxx
170     */
171     if (argc >= 3) { //3:argc of main
172         if (strcmp(argv[1], "sh") == 0 && strcmp(argv[2], "-c") == 0) { //2:argv 2 of main
173             // argvNew[0] is "/system/bin/sh"
174             argvTmp[argvNewIndex++] = StrDup(DEFAULT_BASH);
175             // argvNew[1] is "-c"
176             argvTmp[argvNewIndex++] = StrDup("-c");
177             ret = sprintf_s(cmd, cmdLen, "%s", DEFAULT_BASH);
178             if (ret < 0) {
179                 FreeArgvNew(argvTmp);
180                 return nullptr;
181             }
182             startCopyArgvIndex = 3; //3:start copy index of argv
183             isShc = true;
184         }
185     }
186 
187     /*
188      * if not "sudo sh -c xxxx", just as "sudo xxxx"
189     */
190     if (!isShc) {
191         ret = sprintf_s(cmd, cmdLen, "%s", argv[1]);
192         if (ret < 0 || !GetCmdInPath(cmd, cmdLen, env)) {
193             FreeArgvNew(argvTmp);
194             return nullptr;
195         }
196         argvTmp[argvNewIndex++] = StrDup(cmd);
197         startCopyArgvIndex = 2; //2:start copy index of argv
198     }
199 
200     for (int i = startCopyArgvIndex; i < argc; i++) {
201         argvTmp[argvNewIndex++] = StrDup(argv[i]);
202     }
203     argvTmp[argvNewIndex] = nullptr;
204 
205     return argvTmp;
206 }
207 
GetUserPwd(char *pwdBuf, int bufLen)208 static void GetUserPwd(char *pwdBuf, int bufLen)
209 {
210     const char *prompts = "[sudo] password for current user:";
211     const char *newline = "\n";
212     struct termios oldTerm;
213     struct termios newTerm;
214 
215     WriteTty(prompts);
216 
217     tcgetattr(STDIN_FILENO, &oldTerm);
218     newTerm = oldTerm;
219     newTerm.c_lflag &= ~(ECHO);
220     tcsetattr(STDIN_FILENO, TCSANOW, &newTerm);
221     (void)fgets(pwdBuf, bufLen, stdin);
222     if (pwdBuf[strlen(pwdBuf) - 1] == '\n') {
223         pwdBuf[strlen(pwdBuf) - 1] = '\0';
224     }
225     tcsetattr(STDIN_FILENO, TCSANOW, &oldTerm);
226 
227     WriteTty(newline);
228 }
229 
SetUidGid(void)230 static bool SetUidGid(void)
231 {
232     if (setuid(0) != 0) {
233         return false;
234     }
235     if (setegid(0) != 0) {
236         return false;
237     }
238     if (setgid(0) != 0) {
239         return false;
240     }
241     return true;
242 }
243 
WaitForAuth(void)244 static void WaitForAuth(void)
245 {
246     std::unique_lock<std::mutex> lock(g_mutexForAuth);
247     g_condVarForAuth.wait(lock, [] { return g_authFinish; });
248 }
249 
VerifyAccount(int32_t userId)250 static bool VerifyAccount(int32_t userId)
251 {
252     std::vector<uint8_t> challenge;
253     AuthOptions authOptions;
254     bool verifyResult = false;
255 
256     AccountIAMClient &sudoIAMClient = AccountIAMClient::GetInstance();
257     std::shared_ptr<IDMCallback> callback = std::make_shared<SudoIDMCallback>();
258     authOptions.accountId = userId;
259     sudoIAMClient.AuthUser(authOptions, challenge, AuthType::PIN, AuthTrustLevel::ATL1, callback);
260     std::shared_ptr<SudoIDMCallback> sudoCallback = std::static_pointer_cast<SudoIDMCallback>(callback);
261     WaitForAuth();
262     verifyResult = sudoCallback->GetVerifyResult();
263     return verifyResult;
264 }
265 
UserAccountVerify(char *pwd, int pwdLen)266 static bool UserAccountVerify(char *pwd, int pwdLen)
267 {
268     std::shared_ptr<PinAuth::IInputer> inputer = nullptr;
269     OHOS::ErrCode err;
270     int verifyResult = 0;
271     pid_t pid;
272     int fds[2];
273 
274     if (pipe(fds) != 0) {
275         WriteStdErr("exec pipe failed\n");
276         return false;
277     }
278     pid = fork();
279     if (pid == -1) {
280         WriteStdErr("exec fork failed\n");
281         return false;
282     }
283     if (pid == 0) {
284         int32_t userId = -1;
285         close(fds[0]);
286         err = OsAccountManager::GetForegroundOsAccountLocalId(userId);
287         if (err != 0) {
288             WriteStdErr("get os account local id failed\n");
289             exit(1);
290         }
291         inputer = std::make_shared<PinAuth::SudoIInputer>();
292         std::shared_ptr<PinAuth::SudoIInputer> sudoInputer = std::static_pointer_cast<PinAuth::SudoIInputer>(inputer);
293         sudoInputer->SetPasswd(pwd, pwdLen);
294         err = AccountIAMClient::GetInstance().RegisterPINInputer(inputer);
295         if (err != 0) {
296             WriteStdErr("register pin inputer failed\n");
297             exit(1);
298         }
299         if (VerifyAccount(userId)) {
300             verifyResult = 1;
301         }
302         AccountIAMClient::GetInstance().UnregisterPINInputer();
303         write(fds[1], &verifyResult, sizeof(verifyResult));
304         close(fds[1]);
305         exit(0);
306     } else {
307         close(fds[1]);
308         waitpid(pid, nullptr, 0);
309         read(fds[0], &verifyResult, sizeof(verifyResult));
310         close(fds[0]);
311         return (verifyResult == 1);
312     }
313 }
314 
VerifyUserPin(void)315 static bool VerifyUserPin(void)
316 {
317     char passwd[PWD_BUF_LEN] = {0};
318     bool pwdVerifyResult = false;
319 
320     if (getuid() == 0) {
321         return true;
322     }
323 
324     GetUserPwd(passwd, PWD_BUF_LEN);
325     pwdVerifyResult = UserAccountVerify(passwd, strnlen(passwd, PWD_BUF_LEN));
326     memset_s(passwd, sizeof(passwd), 0, sizeof(passwd));
327     if (!pwdVerifyResult) {
328         WriteTty(USER_VERIFY_FAILED);
329     }
330     return pwdVerifyResult;
331 }
332 
main(int argc, char* argv[], char* env[])333 int main(int argc, char* argv[], char* env[])
334 {
335     char execCmd[PATH_MAX + 1] = {0};
336     char **argvNew = nullptr;
337     const char *help = "sudo - execute command as root\n\n"
338                        "usage: sudo command ...\n"
339                        "usage: sudo sh -c command ...\n";
340     if (argc < 2) { //2:argc check number
341         WriteStdErr(help);
342         return 1;
343     }
344 
345     /*
346      * Get and verify user pwd
347     */
348     if (!VerifyUserPin()) {
349         return 1;
350     }
351 
352     /*
353      * Make exec cmd and the args
354     */
355     argvNew = ParseCmd(argc, argv, env, execCmd, PATH_MAX + 1);
356     if (argvNew == nullptr) {
357         return 1;
358     }
359     CloseTty();
360 
361     /*
362      * set uid, gid, egid
363     */
364     if (!SetUidGid()) {
365         FreeArgvNew(argvNew);
366         WriteStdErr("setuid failed\n");
367         return 1;
368     }
369 
370 #if defined(SURPPORT_SELINUX)
371     setcon("u:r:privilege_app:s0");
372 #endif
373 
374     execvp(execCmd, argvNew);
375 
376     WriteStdErr("execvp failed\n");
377     return 1;
378 }
379