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 36using namespace OHOS::UserIam; 37using namespace OHOS::AccountSA; 38 39static FILE *g_ttyFp = nullptr; 40 41static const char *OUT_OF_MEM = "[E0001] out of memory\n"; 42static const char *COMMAND_NOT_FOUND = "[E0002] command not found\n"; 43static const char *USER_VERIFY_FAILED = "[E0003] Sorry, try again. If screen lock password not set, set it first.\n"; 44 45static void WriteStdErr(const char *str) 46{ 47 (void)fwrite(str, 1, strlen(str), stderr); 48 fflush(stderr); 49} 50 51static 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 67static void CloseTty(void) 68{ 69 if (g_ttyFp != nullptr) { 70 fclose(g_ttyFp); 71 } 72 g_ttyFp = nullptr; 73} 74 75static 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 91static 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*/ 103static 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 146static 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 208static 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 230static 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 244static void WaitForAuth(void) 245{ 246 std::unique_lock<std::mutex> lock(g_mutexForAuth); 247 g_condVarForAuth.wait(lock, [] { return g_authFinish; }); 248} 249 250static 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 266static 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 315static 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 333int 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