xref: /developtools/hdc/sudo/src/main.cpp (revision cc290419)
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