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