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 "os_account_manager.h"
29 #include "sudo_iam.h"
30 #include "user_auth_client.h"
31 #include "pinauth_register.h"
32 #include "user_access_ctrl_client.h"
33 #include "aclmgr_system_api.h"
34
35 #define PWD_BUF_LEN 128
36 #define CHALLENGE_LEN 32
37 #define DEFAULT_PATH "/system/bin"
38 #define DEFAULT_BASH "/system/bin/sh"
39 #define PATH "PATH="
40 using namespace OHOS::UserIam;
41 using namespace OHOS::AccountSA;
42 using namespace OHOS::UserIam::PinAuth;
43 using namespace OHOS::UserIam::UserAuth;
44
45 static FILE *g_ttyFp = nullptr;
46
47 static const char *OUT_OF_MEM = "[E0001] out of memory\n";
48 static const char *COMMAND_NOT_FOUND = "[E0002] command not found\n";
49 static const char *USER_VERIFY_FAILED = "[E0003] Sorry, try again. If screen lock password not set, set it first.\n";
50 static const char *HELP = "sudo - execute command as root\n\n"
51 "usage: sudo command ...\n"
52 "usage: sudo sh -c command ...\n";
53
54 static int32_t g_userId = -1;
55 static std::vector<uint8_t> g_challenge(CHALLENGE_LEN, 0);
56 static std::vector<uint8_t> g_authToken;
57 static const std::string CONSTRAINT_SUDO = "constraint.sudo";
58
WriteStdErr(const char * str)59 static void WriteStdErr(const char *str)
60 {
61 (void)fwrite(str, 1, strlen(str), stderr);
62 fflush(stderr);
63 }
64
WriteTty(const char * str)65 static void WriteTty(const char *str)
66 {
67 if (g_ttyFp != nullptr) {
68 (void)fwrite(str, 1, strlen(str), g_ttyFp);
69 fflush(g_ttyFp);
70 } else {
71 g_ttyFp = fopen("/dev/tty", "w");
72 if (g_ttyFp != nullptr) {
73 (void)fwrite(str, 1, strlen(str), g_ttyFp);
74 fflush(g_ttyFp);
75 return;
76 }
77 WriteStdErr("open /dev/tty for write failed\n");
78 }
79 }
80
CloseTty(void)81 static void CloseTty(void)
82 {
83 if (g_ttyFp != nullptr) {
84 fclose(g_ttyFp);
85 }
86 g_ttyFp = nullptr;
87 }
88
StrDup(const char * str)89 static char *StrDup(const char *str)
90 {
91 int ret;
92 char *result = new(std::nothrow)char[strlen(str) + 1];
93 if (result == nullptr) {
94 WriteStdErr(OUT_OF_MEM);
95 exit(1);
96 }
97 ret = strcpy_s(result, strlen(str) + 1, str);
98 if (ret != 0) {
99 WriteStdErr(OUT_OF_MEM);
100 exit(1);
101 }
102 return result;
103 }
104
FreeArgvNew(char ** argvNew)105 static void FreeArgvNew(char **argvNew)
106 {
107 char **p = nullptr;
108 for (p = argvNew; *p != nullptr; p++) {
109 delete [] *p;
110 }
111 delete [] argvNew;
112 }
113
114 /*
115 * Find cmd from PATH
116 */
GetCmdInPath(char * cmd,int cmdBufLen,char * envp[])117 static bool GetCmdInPath(char *cmd, int cmdBufLen, char *envp[])
118 {
119 struct stat st;
120 char *path = nullptr;
121 char *pathBak = nullptr;
122 char **ep = nullptr;
123 char *cp = nullptr;
124 char pathBuf[PATH_MAX + 1] = {0};
125 bool findSuccess = false;
126
127 if (strchr(cmd, '/') != nullptr) {
128 return true;
129 }
130
131 for (ep = envp; *ep != nullptr; ep++) {
132 if (strncmp(*ep, PATH, strlen(PATH)) == 0) {
133 path = *ep + strlen(PATH);
134 break;
135 }
136 }
137
138 path = StrDup((path != nullptr && *path != '\0') ? path : DEFAULT_PATH);
139 pathBak = path;
140 do {
141 if ((cp = strchr(path, ':')) != nullptr) {
142 *cp = '\0';
143 }
144 int ret = sprintf_s(pathBuf, sizeof(pathBuf), "%s/%s", *path ? path : ".", cmd);
145 if (ret > 0 && stat(pathBuf, &st) == 0 && S_ISREG(st.st_mode)) {
146 findSuccess = true;
147 break;
148 }
149 path = cp + 1;
150 } while (cp != nullptr);
151
152 delete [] pathBak;
153 if (!findSuccess) {
154 WriteTty(COMMAND_NOT_FOUND);
155 return false;
156 }
157 return (sprintf_s(cmd, cmdBufLen, "%s", pathBuf) < 0) ? false : true;
158 }
159
ParseCmd(int argc,char * argv[],char * env[],char * cmd,int cmdLen)160 static char **ParseCmd(int argc, char* argv[], char* env[], char *cmd, int cmdLen)
161 {
162 int startCopyArgvIndex = 1;
163 int argvNewIndex = 0;
164 char **argvTmp = nullptr;
165 bool isShc = false;
166 int ret;
167
168 /*
169 * Here, we construct the command and its argv
170 * sudo sh -c xxx yyy -----> sh -c xxx yyy
171 * sudo xxx yyy -----> xxx yyy
172 */
173 if (argc <= 0) {
174 return nullptr;
175 }
176 argvTmp = new(std::nothrow) char* [argc];
177 if (argvTmp == nullptr) {
178 WriteStdErr(OUT_OF_MEM);
179 return nullptr;
180 }
181 (void)memset_s(argvTmp, sizeof(char*) * argc, 0, sizeof(char*) * argc);
182 /*
183 * sudo sh -c xxxx
184 */
185 if (argc >= 3) { //3:argc of main
186 if (strcmp(argv[1], "sh") == 0 && strcmp(argv[2], "-c") == 0) { //2:argv 2 of main
187 // argvNew[0] is "/system/bin/sh"
188 argvTmp[argvNewIndex++] = StrDup(DEFAULT_BASH);
189 // argvNew[1] is "-c"
190 argvTmp[argvNewIndex++] = StrDup("-c");
191 ret = sprintf_s(cmd, cmdLen, "%s", DEFAULT_BASH);
192 if (ret < 0) {
193 FreeArgvNew(argvTmp);
194 return nullptr;
195 }
196 startCopyArgvIndex = 3; //3:start copy index of argv
197 isShc = true;
198 }
199 }
200
201 /*
202 * if not "sudo sh -c xxxx", just as "sudo xxxx"
203 */
204 if (!isShc) {
205 ret = sprintf_s(cmd, cmdLen, "%s", argv[1]);
206 if (ret < 0 || !GetCmdInPath(cmd, cmdLen, env)) {
207 FreeArgvNew(argvTmp);
208 return nullptr;
209 }
210 argvTmp[argvNewIndex++] = StrDup(cmd);
211 startCopyArgvIndex = 2; //2:start copy index of argv
212 }
213
214 for (int i = startCopyArgvIndex; i < argc; i++) {
215 argvTmp[argvNewIndex++] = StrDup(argv[i]);
216 }
217 argvTmp[argvNewIndex] = nullptr;
218
219 return argvTmp;
220 }
221
GetUserPwd(char * pwdBuf,int bufLen)222 static void GetUserPwd(char *pwdBuf, int bufLen)
223 {
224 const char *prompts = "[sudo] password for current user:";
225 const char *newline = "\n";
226 struct termios oldTerm;
227 struct termios newTerm;
228
229 WriteTty(prompts);
230
231 tcgetattr(STDIN_FILENO, &oldTerm);
232 newTerm = oldTerm;
233 newTerm.c_lflag &= ~(ECHO);
234 tcsetattr(STDIN_FILENO, TCSANOW, &newTerm);
235 (void)fgets(pwdBuf, bufLen, stdin);
236 if (pwdBuf[strlen(pwdBuf) - 1] == '\n') {
237 pwdBuf[strlen(pwdBuf) - 1] = '\0';
238 }
239 tcsetattr(STDIN_FILENO, TCSANOW, &oldTerm);
240
241 WriteTty(newline);
242 }
243
SetUidGid(void)244 static bool SetUidGid(void)
245 {
246 if (setuid(0) != 0) {
247 return false;
248 }
249 if (setegid(0) != 0) {
250 return false;
251 }
252 if (setgid(0) != 0) {
253 return false;
254 }
255 return true;
256 }
257
WaitForAuth(void)258 static void WaitForAuth(void)
259 {
260 std::unique_lock<std::mutex> lock(g_mutexForAuth);
261 g_condVarForAuth.wait(lock, [] { return g_authFinish; });
262 }
263
GetChallenge()264 static bool GetChallenge()
265 {
266 int32_t res = InitChallengeForCommand(g_challenge.data(), g_challenge.size());
267 if (res != 0) {
268 WriteStdErr("init challenge failed\n");
269 return false;
270 }
271 return true;
272 }
273
VerifyAccount()274 static bool VerifyAccount()
275 {
276 bool verifyResult = false;
277
278 UserAuthClient &sudoIAMClient = UserAuthClient::GetInstance();
279 std::shared_ptr<UserAuth::AuthenticationCallback> callback = std::make_shared<SudoIDMCallback>();
280
281 OHOS::UserIam::UserAuth::AuthParam authParam;
282 authParam.userId = g_userId;
283 authParam.challenge = g_challenge;
284 authParam.authType = AuthType::PIN;
285 authParam.authTrustLevel = AuthTrustLevel::ATL1;
286
287 sudoIAMClient.BeginAuthentication(authParam, callback);
288 std::shared_ptr<SudoIDMCallback> sudoCallback = std::static_pointer_cast<SudoIDMCallback>(callback);
289 WaitForAuth();
290 verifyResult = sudoCallback->GetVerifyResult();
291 g_authToken = sudoCallback->GetAuthToken();
292 return verifyResult;
293 }
294
GetUserId()295 static bool GetUserId()
296 {
297 std::vector<int32_t> ids;
298
299 OHOS::ErrCode err = OsAccountManager::QueryActiveOsAccountIds(ids);
300 if (err != 0) {
301 WriteStdErr("get os account local id failed\n");
302 return false;
303 }
304
305 g_userId = ids[0];
306 return true;
307 }
308
UserAccountVerify(char * pwd,int pwdLen)309 static bool UserAccountVerify(char *pwd, int pwdLen)
310 {
311 bool verifyResult = false;
312 std::shared_ptr<PinAuth::IInputer> inputer = nullptr;
313
314 inputer = std::make_shared<PinAuth::SudoIInputer>();
315 std::shared_ptr<PinAuth::SudoIInputer> sudoInputer = std::static_pointer_cast<PinAuth::SudoIInputer>(inputer);
316 sudoInputer->SetPasswd(pwd, pwdLen);
317 if (!PinAuthRegister::GetInstance().RegisterInputer(inputer)) {
318 WriteStdErr("register pin inputer failed\n");
319 return false;
320 }
321
322 if (VerifyAccount()) {
323 verifyResult = true;
324 }
325
326 PinAuthRegister::GetInstance().UnRegisterInputer();
327 return verifyResult;
328 }
329
VerifyUserPin()330 static bool VerifyUserPin()
331 {
332 char passwd[PWD_BUF_LEN] = {0};
333 bool pwdVerifyResult = false;
334
335 if (getuid() == 0) {
336 return true;
337 }
338
339 GetUserPwd(passwd, PWD_BUF_LEN);
340 pwdVerifyResult = UserAccountVerify(passwd, strnlen(passwd, PWD_BUF_LEN));
341 memset_s(passwd, sizeof(passwd), 0, sizeof(passwd));
342 if (!pwdVerifyResult) {
343 WriteTty(USER_VERIFY_FAILED);
344 }
345 return pwdVerifyResult;
346 }
347
SetPSL()348 static bool SetPSL()
349 {
350 int32_t res = SetProcessLevelByCommand(g_authToken.data(), g_authToken.size());
351 if (res != 0) {
352 WriteStdErr("set psl failed\n");
353 return false;
354 }
355 return true;
356 }
357
358 #if defined(SURPPORT_ACCOUNT_CONSTRAINT)
CheckUserLimitation()359 static bool CheckUserLimitation()
360 {
361 bool isNotEnabled = false;
362 OHOS::ErrCode err = OsAccountManager::CheckOsAccountConstraintEnabled(
363 g_userId, CONSTRAINT_SUDO, isNotEnabled);
364 if (err != 0) {
365 WriteStdErr("check account constrain failed.\n");
366 return false;
367 }
368
369 if (isNotEnabled) {
370 WriteStdErr("no permision.\n");
371 return false;
372 }
373
374 return true;
375 }
376 #endif
377
main(int argc,char * argv[],char * env[])378 int main(int argc, char* argv[], char* env[])
379 {
380 if (!GetUserId()) {
381 WriteStdErr("get user id failed.\n");
382 return 1;
383 }
384
385 #if defined(SURPPORT_ACCOUNT_CONSTRAINT)
386 if (!CheckUserLimitation()) {
387 return 1;
388 }
389 #endif
390
391 if (argc < 2) { // 2:argc check number
392 WriteStdErr(HELP);
393 return 1;
394 }
395
396 if (!GetChallenge()) {
397 return 1;
398 }
399
400 if (!VerifyUserPin()) {
401 return 1;
402 }
403
404 if (!SetPSL()) {
405 return 1;
406 }
407
408 char execCmd[PATH_MAX + 1] = {0};
409 char **argvNew = ParseCmd(argc, argv, env, execCmd, PATH_MAX + 1);
410 CloseTty();
411 if (argvNew == nullptr) {
412 return 1;
413 }
414
415 if (!SetUidGid()) {
416 FreeArgvNew(argvNew);
417 WriteStdErr("setuid failed\n");
418 return 1;
419 }
420
421 #if defined(SURPPORT_SELINUX)
422 if (setcon("u:r:sudo_execv_label:s0") != 0) {
423 WriteStdErr("set SEHarmony label failed\n");
424 return 1;
425 }
426 #endif
427
428 execvp(execCmd, argvNew);
429 WriteStdErr("execvp failed\n");
430 return 1;
431 }
432