Real user ID, effective user ID, set user ID

前段时间一直没搞明白这几个ID之间的关系,今天看到一篇博文,这才拨云见日,才有所了解了.
real user ID:实际用户ID,指的是进程执行者是谁
effective user ID:有效用户ID,指进程执行时对文件的访问权限
saved set-user-ID:保存设置用户ID,作为effective user ID的副本,在执行exec调用时后能重新恢复原来的effectiv user ID.
上面这三个ID是相对于进程而言的.
set-user-ID:设置用户ID,这是相对于文件来说的.设置了set-user-ID位的可执行程序,执行时,进程的effective user ID与saved set-uesr-ID都为程序文件所属用户的ID,些时real user ID与effective user ID就不一定相等了.这类程序称之为SUID程序,这类程序有特殊的用途.典型的例子:passwd程序,ping程序等.
passwd程序是要修改用户密码,此时是要修改/etc/passwd或修改/etc/shadow文件(有必要时),然而一般用户没有修改这两个文件的权限.passwd程序设置了set-user-ID位的,并且该文件的所有都是root,所以,一般用户执行时,也具有了root的权限.
ping程序也如此,因为ping程序要产生原始套接字(raw),所以需要有root的权限.然而一般用户之所以能用ping程序,就是因为ping程序的所有都是root用户,并且它设置了set-user-ID位.
为一可执行程序设置set-user-ID位:
pds@FSSR:~> su root
口令:
FSSR:/home/pds # chown root suid
FSSR:/home/pds # ll suid
-rwxr-xr-x 1 root users 7702 2008-08-10 11:28 suid
FSSR:/home/pds # chmod u+s suid
FSSR:/home/pds # ll suid
-rwsr-xr-x 1 root users 7702 2008-08-10 11:28 suid
FSSR:/home/pds # exit
exit

相对的,没有设置set-user-ID位的可执行程序,称之为非SUID程序,该程序执行时,real user ID与effective user ID相等.
setuid可以修改real user ID,effective user ID和saved set-user-ID这三个值,但是要用权限.
这是函数原型:
int setuid(uid_t uid)
1.如果用户(当前调用的用户)有超级用户权限,则real user ID,effective user ID和saved set-user-ID都将设置为参数uid的值.
2.如果用户没有超级用户权限,仅当参数uid等于real user ID或saved set-user-ID时,effective user ID被设置为参数uid的值,real user ID和saved set-user-ID不变;否则返回错误.
这是自己按自己的理解总结的 ^_^

下面看看别人写的:
Unix中常见的几个概念,下面做一个解释.
首先需要明确一点,这几个概念都是和进程相关的.
real user ID表示的是实际上进程的执行者是谁,effective user ID主要用于校验该进程在执行时所获得的文件访问权限,也就是说当进程访问
文件时检查权限时实际上检查的该进程的"effective user ID",saved set-user-ID 仅在effective user ID发生改变时保存.
一般情况下,real user ID就是进程的effective user ID,但是当要运行的可执行程序设置了"set-user-ID"位之后,进程的effective user ID
变成该文件的属主用户id,同时该进程的"saved set-user-ID"变成此时进程的"effective user ID",也就是该可执行程序的属主用户ID,该进程
在执行一些与文件访问权限相关的操作时系统检查的是进程的effective user ID.
为什么需要一个"saved set-user-ID"?因为当进程没有超级用户权限的时候,进程在设置"effective user ID"时需要将需要设置的ID和该进程
的"real user ID"或者"saved set-user-ID"进行比较.
APUE2中进行的解释是:
1)If the process has superuser privileges, the setuid function sets the real user ID, effective user ID, and saved set-user-
ID to uid.
2)If the process does not have superuser privileges, but uid equals either the real user ID or the saved set-user-ID, setuid
sets only the effective user ID to uid. The real user ID and the saved set-user-ID are not changed.
3)If neither of these two conditions is true, errno is set to EPERM, and 1 is returned
也就是说:
1)当用户具有超级用户权限的时候,setuid 函数设置的id对三者都起效.
2)否则,仅当该id为real user ID 或者saved set-user-ID时,该id对effective user ID起效.
3)否则,setuid函数调用失败.
也就是说,这个saved set-user-ID更多的作用是在进程切换自己的effective user ID起作用.
需要特别提醒的是:并没有任何的API可以获取到进程的saved set-user-ID,它仅仅是系统在调用setuid函数时进行比较而起作用的.
APUE2中关于此事的原话如下:
Note that we can obtain only the current value of the real user ID and the effective user ID with the functions getuid and
geteuid from Section 8.2. We can't obtain the current value of the saved set-user-ID.

举一个例子说明问题,假设这样的一种情况,系统中有两个用户A,B,还有一个由B创建的可执行程序proc,该可执行程序的set-
user-id位已经进行了设置.
当A用户执行程序proc时,
程序的real user ID = A的用户ID,effective user ID = B的用户ID, saved set-user-ID=B的用户ID.
假如在该进程结束了对某些限制只能由用户B访问的文件操作后,程序将effective user ID设置回A,也就是说此时:
程序的real user ID = A的用户ID,effective user ID = A的用户ID, saved set-user-ID=B的用户ID.
这个改动之所以能成功,原因在于上面列举出的情况2):该ID为进程的real user ID.
最后,假设由于种种原因进程需要再次切换effective user ID为B,可是因为不能通过API获取进程的saved set-user-ID(该值为B的用户ID),所
以只能通过两种途径获得(可能还有别的途径):
a)在设置effective user ID变回A之前保存effective user ID,它的值为B的用户ID.
b)调用函数getpwnam( "B"),在返回的struct passwd *指针中成员pw_uid存放的就是用户B的ID.
这样,这个调用setuid(B的用户ID)就会成功,原因也在于上面说的情况2):该ID与进程的saved set-user-ID相同.
APUE2中关于这几个值的相关解释在section4.4和section8.11中都有涉及.
else if (strcmp(action, "7") == 0) { int jingpin_id; char new_price; // 解析参数(格式:"7 竞品编号 金额") if (sscanf(buffer, "%*s %d %s", &jingpin_id, &new_price) != 2) { send(connfd, msg20, strlen(msg20), 0); // 自动计算长度 return 0; } // 开启事务保证原子性 sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0); // 检查竞拍品状态 const char *check_sql = "SELECT qipaijia, zuigaojia, maijia FROM jingpin WHERE bianhao = ?;"; sqlite3_stmt *check_stmt; rc = sqlite3_prepare_v2(db, check_sql, -1, &check_stmt, NULL); if (rc != SQLITE_OK || sqlite3_bind_int(check_stmt, 1, jingpin_id) != SQLITE_OK) { send(connfd, msg21, strlen(msg21), 0); // 自动计算长度 goto rollback; } if (sqlite3_step(check_stmt) != SQLITE_ROW) { send(connfd, msg22, strlen(msg22), 0); // 自动计算长度 goto cleanup; } char qipaijia = sqlite3_column_text(check_stmt, 0); char current_max = sqlite3_column_text(check_stmt, 1); int maijia_status = sqlite3_column_type(check_stmt, 2); // 验证逻辑 if (maijia_status != SQLITE_NULL) { send(connfd, msg23, strlen(msg23), 0); // 自动计算长度 goto cleanup; } double effective_max = (current_max > 0) ? current_max : qipaijia; if (new_price <= effective_max) { send(connfd, msg24, strlen(msg24), 0); // 自动计算长度 goto cleanup; } // 更新最高价 const char *update_sql = "UPDATE jingpin SET zuigaojia = ? WHERE bianhao = ?;"; sqlite3_stmt *update_stmt; if (sqlite3_prepare_v2(db, update_sql, -1, &update_stmt, NULL) != SQLITE_OK || sqlite3_bind_double(update_stmt, 1, new_price) != SQLITE_OK || sqlite3_bind_int(update_stmt, 2, jingpin_id) != SQLITE_OK) { send(connfd, msg25, strlen(msg25), 0); // 自动计算长度 goto rollback; } if (sqlite3_step(update_stmt) != SQLITE_DONE) { send(connfd, msg25, strlen(msg25), 0); // 自动计算长度 goto rollback; } // 记录出价历史 const char *log_sql = "INSERT INTO chujia (jingpin_id, user_id, price) VALUES (?, ?, ?);"; sqlite3_stmt *log_stmt; if (sqlite3_prepare_v2(db, log_sql, -1, &log_stmt, NULL) != SQLITE_OK || sqlite3_bind_int(log_stmt, 1, jingpin_id) != SQLITE_OK || sqlite3_bind_int(log_stmt, 2, c->user_id) != SQLITE_OK || sqlite3_bind_double(log_stmt, 3, new_price) != SQLITE_OK) { send(connfd, msg26, strlen(msg26), 0); // 自动计算长度 goto rollback; } if (sqlite3_step(log_stmt) != SQLITE_DONE) { send(connfd, msg26, strlen(msg26), 0); // 自动计算长度 goto rollback; } // 提交事务 sqlite3_exec(db, "COMMIT", 0, 0, 0); send(connfd, msg27, strlen(msg27), 0); // 自动计算长度 cleanup: sqlite3_finalize(check_stmt); sqlite3_finalize(update_stmt); sqlite3_finalize(log_stmt); return 1; rollback: sqlite3_exec(db, "ROLLBACK", 0, 0, 0); goto cleanup; }检查错误
最新发布
04-01
<think>嗯,用户给了一段C语言的代码,是关于处理竞拍品价格更新的逻辑,需要检查其中的错误。首先,我得通读一遍代码,看看有没有明显的语法或逻辑问题。 首先看变量声明部分。int jingpin_id; 这里没问题,但接下来char new_price;可能有问题。因为价格应该是数值类型,比如double或float,而char只能存字符,这显然不对。可能应该是double new_price;,这样后面使用sscanf解析的时候,格式符也应该是%lf而不是%s。但用户代码里用的是%s,这会导致将字符串读入char变量,显然会导致错误,甚至缓冲区溢出。 接下来是sscanf的解析部分:sscanf(buffer, "%*s %d %s", &jingpin_id, &new_price)。这里new_price是char类型,用%s来读取字符串,但变量是单个char,无法存储字符串,这里应该用%lf或者对应的类型,并且变量类型要匹配。所以这里肯定有问题,会导致输入解析错误,进而触发参数不足的错误提示msg20。 然后检查SQL部分,比如查询语句中的字段qipaijia、zuigaojia、maijia。假设数据库里这些字段的类型,比如qipaijiazuigaojia应该是数值类型,比如REAL或INTEGER,而代码中使用sqlite3_column_text来获取,这会导致将数值转换为字符串,可能引发错误。比如char qipaijia = sqlite3_column_text(check_stmt, 0);这里qipaijia是char类型,但sqlite3_column_text返回的是const char*,直接赋值给char会导致截断或错误。正确的做法应该是用sqlite3_column_double来获取数值,并存到double变量中。 同样,current_maxnew_price的比较也存在类型不匹配的问题。current_max被错误地以字符串形式获取,而new_price如果是char类型,比较时可能按ASCII值比较,而非数值。这会导致逻辑错误,比如价格判断不正确。 接下来,检查事务处理部分。在开启事务后,如果出现错误,代码使用了goto rollback;cleanup;标签。但需要注意的是,在cleanup标签处,会执行sqlite3_finalize,但某些stmt可能在失败时未被正确初始化,比如如果sqlite3_prepare_v2失败,stmt可能为NULL,直接finalize会导致问题。不过SQLite允许传递NULL给finalize,所以这可能没问题。 另外,在验证maijia_status时,判断是否为SQLITE_NULL。maijia字段如果是INTEGER类型,当没有买家时可能为NULL,所以这个检查是对的。但如果maijia字段是TEXT或其他类型,可能需要调整。 还有,在更新最高价时,sqlite3_bind_double用于new_price,但此时new_price如果是char类型,会导致错误的数据绑定,进而导致数据库操作失败,触发msg25的错误提示。 此外,记录出价历史的INSERT语句中,user_id可能来自结构体c->user_id,这里假设c是有效的,需要确保c指针不为空,否则会导致段错误。 最后,代码中使用了多个goto语句,需要注意流程是否正确,特别是在rollback之后跳转到cleanup,是否正确释放了所有stmt。同时,在cleanup部分,即使某些stmt未初始化,调用sqlite3_finalize是否安全,但根据SQLite文档,传入NULL是安全的。 总结可能的错误点: 1. new_price的类型错误,应为double而非char,导致sscanf解析错误后续绑定错误。 2. sscanf中使用%s读取数值到char变量,导致缓冲区溢出解析错误。 3. 使用sqlite3_column_text获取数值字段,导致错误的数据类型转换。 4. 变量current_maxqipaijia以字符串形式处理,导致比较逻辑错误。 5. sqlite3_bind_double绑定到错误类型的变量(new_price应为double但声明为char)。 6. 可能的字符串与数值比较错误,如current_max > 0,如果current_max是字符串,比较不正确。 修正步骤: - 将new_price声明为double。 - 修改sscanf的格式字符串为"%d %lf",并使用&new_price(注意类型匹配)。 - 使用sqlite3_column_double获取qipaijiazuigaojia的值。 - 修复变量类型,确保所有价格比较使用数值类型。</think>这段代码存在多处数据类型逻辑错误,以下分步骤说明问题解决方案: --- ### 一、数据类型错误 #### 1. `new_price`类型错误 ```c char new_price; // 错误:价格应为数值类型 ``` 应改为: ```c double new_price; // 正确:价格需支持小数 ``` #### 2. `sscanf`格式错误 ```c sscanf(buffer, "%*s %d %s", &jingpin_id, &new_price); // 错误:%s用于字符串,无法读取数值 ``` 应改为: ```c sscanf(buffer, "%*s %d %lf", &jingpin_id, &new_price); // 正确:%lf读取double类型 ``` #### 3. 数据库字段类型错误 ```c char qipaijia = sqlite3_column_text(check_stmt, 0); // 错误:将数值字段读为字符串 char current_max = sqlite3_column_text(check_stmt, 1); // 错误 ``` 应改为: ```c double qipaijia = sqlite3_column_double(check_stmt, 0); // 正确:读取数值类型 double current_max = sqlite3_column_double(check_stmt, 1); ``` --- ### 二、逻辑错误 #### 1. 最高价验证逻辑 ```c double effective_max = (current_max > 0) ? current_max : qipaijia; if (new_price <= effective_max) { /* 拒绝报价 */ } ``` 问题:若`current_max`未初始化(如首次出价),逻辑可能错误。 改进建议: ```c double effective_max = (current_max > qipaijia) ? current_max : qipaijia; ``` #### 2. 事务回滚处理 `goto cleanup`后未回滚事务,需调整: ```c if (sqlite3_step(check_stmt) != SQLITE_ROW) { send(...); goto rollback; // 原代码中为goto cleanup,导致事务未回滚 } ``` --- ### 三、代码健壮性改进 #### 1. 指针空值检查 ```c if (c == NULL) { // 确保用户信息有效 send(connfd, "内部错误", ...); goto rollback; } ``` #### 2. SQL语句参数检查 绑定参数时需检查返回值: ```c if (sqlite3_bind_double(update_stmt, 1, new_price) != SQLITE_OK) { // 错误处理 } ``` --- ### 四、修正后的关键代码片段 ```c else if (strcmp(action, "7") == 0) { int jingpin_id; double new_price; // 修正为double // 解析参数(格式:"7 竞品编号 金额") if (sscanf(buffer, "%*s %d %lf", &jingpin_id, &new_price) != 2) { send(connfd, msg20, strlen(msg20), 0); return 0; } // 开启事务 sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0); // 检查竞拍品状态 const char *check_sql = "SELECT qipaijia, zuigaojia, maijia FROM jingpin WHERE bianhao = ?;"; sqlite3_stmt *check_stmt; // ...(准备语句并绑定参数) // 读取数值字段 double qipaijia = sqlite3_column_double(check_stmt, 0); double current_max = sqlite3_column_double(check_stmt, 1); // 验证逻辑 double effective_max = (current_max > qipaijia) ? current_max : qipaijia; if (new_price <= effective_max) { send(connfd, msg24, strlen(msg24), 0); goto rollback; } // ...(后续更新日志记录) } ``` --- ### 五、总结 1. **数据类型匹配**:确保变量类型与数据库字段输入解析一致。 2. **事务完整性**:所有失败路径必须触发`ROLLBACK`。 3. **输入验证**:严格检查输入格式业务逻辑条件。 4. **错误处理**:所有SQLite API调用需检查返回值。 建议通过编译器的`-Wall -Werror`选项捕捉类型不匹配警告,并使用SQLite调试工具验证数据库操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值