Files
ligent/tests/test_priv.c
T
2026-10-03 14:13:21 +08:00

119 lines
5.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_priv.c - 提权模块里那几条"不能搞错"的不变量
*
* 这个模块摔过一跤:上一版把认证路径也 setsid + 接管道,于是 sudo 拿不到
* 控制终端,真机上认证 100% 失败(而替身 sudo 无条件成功,给了个假绿)。
* 所以这里把两条硬不变量钉住:
*
* 1. **提权调用里不能有 -n**。带 -n 就等于"不许提问",需要密码时直接失败,
* 而用户要的恰恰是"提权要有身份验证"。
* 2. **没有控制终端时能识别出来**。识别不出来就会起一个等不到输入的进程。
*/
#include "priv.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static int failures = 0;
static int checks = 0;
#define CHECK(cond, ...) \
do { \
checks++; \
if (!(cond)) { \
failures++; \
printf(" ✗ %s:%d: ", __FILE__, __LINE__); \
printf(__VA_ARGS__); \
printf("\n"); \
} \
} while (0)
static int argv_has(char *const *a, const char *want)
{
for (size_t i = 0; a[i]; i++)
if (strcmp(a[i], want) == 0) return 1;
return 0;
}
int main(void)
{
puts("包装成 sudo -u <用户> -- <argv>");
/* 显式转换,和 priv.c 里 w[0] = (char *)"sudo" 是同一个理由:
* priv_sudo_argv 的签名要 char *const[](execvp 的要求),而这里放进去的
* 字面量没有任何人会改写。把整条链路改成非 const 才是真的丢 const 正确性。 */
char *plain[] = { (char *)"/bin/sh", (char *)"-c", (char *)"rm -rf /tmp/x", NULL };
char **w = priv_sudo_argv("root", plain);
CHECK(w != NULL, "应当返回数组");
CHECK(strcmp(w[0], "sudo") == 0, "第一个是 sudo,实际 %s", w[0]);
CHECK(strcmp(w[1], "-u") == 0 && strcmp(w[2], "root") == 0,
"-u <用户> 紧跟其后");
CHECK(strcmp(w[3], "--") == 0,
"必须有 -- :否则命令里的选项会被 sudo 当成自己的");
CHECK(strcmp(w[4], "/bin/sh") == 0 && strcmp(w[5], "-c") == 0 &&
strcmp(w[6], "rm -rf /tmp/x") == 0,
"原 argv 原样接在后面,不被拆开");
CHECK(w[7] == NULL, "数组要 NULL 结尾");
/* 命门外的那条不变量 */
CHECK(!argv_has(w, "-n"),
"**不能带 -n**:带 -n 就不许提问,需要密码时直接失败 —— "
"而提权的要点就是让 sudo 能问密码");
CHECK(!argv_has(w, "-S"),
"也不能带 -S:那要求把密码从 stdin 喂进去,等于让 ligent 经手密码");
free(w);
puts("用户名为空时按 root");
w = priv_sudo_argv(NULL, plain);
CHECK(strcmp(w[2], "root") == 0, "空用户应落到 root,实际 %s", w[2]);
free(w);
w = priv_sudo_argv("", plain);
CHECK(strcmp(w[2], "root") == 0, "空串也应落到 root");
free(w);
puts("sudo 的失败原话要换成能照做的中文");
char err[512];
memset(err, 0, sizeof err);
priv_explain(1, "sudo: no tty present and no askpass program specified", err, sizeof err);
CHECK(strstr(err, "终端") != NULL, "缺终端要说终端,实际 %s", err);
priv_explain(1, "Sorry, try again.", err, sizeof err);
CHECK(strstr(err, "认证") != NULL, "认证失败要说认证,实际 %s", err);
priv_explain(1, "pluv is not in the sudoers file.", err, sizeof err);
CHECK(strstr(err, "sudo") != NULL && strstr(err, "权限") != NULL,
"没有 sudo 权限要说清楚,实际 %s", err);
/* 认不出来就原样透传:宁可给原文,也不编一句可能不对的解释 */
priv_explain(1, "some brand new message", err, sizeof err);
CHECK(strstr(err, "some brand new message") != NULL,
"认不出来要透传原文,实际 %s", err);
priv_explain(127, "无法执行 sudo:No such file or directory", err, sizeof err);
CHECK(strstr(err, "没有 sudo") != NULL, "没有 sudo 时要说清楚,实际 %s", err);
puts("没有控制终端时能识别出来");
int fds[2];
if (pipe(fds) != 0) { perror("pipe"); return 2; }
pid_t pid = fork();
if (pid < 0) { perror("fork"); return 2; }
if (pid == 0) {
close(fds[0]);
int rc = -1;
if (setsid() >= 0) { /* 摘掉控制终端 */
rc = priv_have_tty(); /* 必须是 0 */
}
ssize_t wr = write(fds[1], &rc, sizeof rc);
(void)wr;
close(fds[1]);
_exit(0);
}
close(fds[1]);
int got = -1;
ssize_t rd = read(fds[0], &got, sizeof got);
close(fds[0]);
while (waitpid(pid, NULL, 0) < 0) { }
CHECK(rd == (ssize_t)sizeof got && got == 0,
"setsid() 之后不该认为有终端(识别不出来就会卡住)");
printf("\n%d 项检查,%d 项失败\n", checks, failures);
return failures ? 1 : 0;
}