-
Notifications
You must be signed in to change notification settings - Fork 126
fix(starry-kernel): pidfd open/getfd/send_signal Linux conformance #707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
04e3344
ed4b316
77f6937
caa4b3d
6861edd
ad19277
b043874
830734d
ac0cdb3
9fde25a
848a8fb
bcec401
5176a22
5e3bfbe
f8fdb86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,19 @@ | ||
| use alloc::sync::Arc; | ||
|
|
||
| use ax_errno::{AxError, AxResult}; | ||
| use ax_task::current; | ||
| use bitflags::bitflags; | ||
| use linux_raw_sys::general::SI_USER; | ||
| use starry_signal::SignalInfo; | ||
| use linux_raw_sys::general::{SI_TKILL, SI_USER}; | ||
| use starry_signal::{SignalInfo, Signo}; | ||
| use starry_vm::VmPtr; | ||
|
|
||
| use crate::{ | ||
| file::{FD_TABLE, FileLike, PidFd, add_file_like}, | ||
| syscall::signal::{check_kill_permission, make_queue_signal_info, make_siginfo}, | ||
| task::{AsThread, get_process_data, get_task, send_signal_to_process}, | ||
| syscall::signal::check_kill_permission, | ||
| task::{ | ||
| AsThread, get_process_data, get_task, send_signal_to_process, send_signal_to_process_group, | ||
| send_signal_to_thread, | ||
| }, | ||
| }; | ||
|
|
||
| bitflags! { | ||
|
|
@@ -17,20 +24,56 @@ bitflags! { | |
| } | ||
| } | ||
|
|
||
| bitflags! { | ||
| #[derive(Debug, Clone, Copy, Default)] | ||
| struct PidFdSignalFlags: u32 { | ||
| const THREAD = 1 << 0; | ||
| const THREAD_GROUP = 1 << 1; | ||
| const PROCESS_GROUP = 1 << 2; | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Copy, PartialEq, Eq)] | ||
| enum PidFdSignalScope { | ||
| Thread, | ||
| ThreadGroup, | ||
| ProcessGroup, | ||
| } | ||
|
|
||
| fn parse_signo(signo: u32) -> AxResult<Signo> { | ||
| Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput) | ||
| } | ||
|
|
||
| fn make_pidfd_siginfo(signo: Signo, scope: PidFdSignalScope) -> SignalInfo { | ||
| let code = if scope == PidFdSignalScope::Thread { | ||
| SI_TKILL | ||
| } else { | ||
| SI_USER as _ | ||
| }; | ||
| let curr = current(); | ||
| let thread = curr.as_thread(); | ||
| SignalInfo::new_user(signo, code, thread.proc_data.proc.pid(), thread.cred().uid) | ||
| } | ||
|
|
||
| pub fn sys_pidfd_open(pid: u32, flags: u32) -> AxResult<isize> { | ||
| debug!("sys_pidfd_open <= pid: {pid}, flags: {flags}"); | ||
|
|
||
| let flags = PidFdFlags::from_bits(flags).ok_or(AxError::InvalidInput)?; | ||
|
|
||
| // Linux pidfd_open(2): EINVAL if pid is not valid. PID 0 is not a | ||
| // userspace-visible process id for this syscall (do not alias to self). | ||
| if pid == 0 { | ||
| // Linux pidfd_open(2): EINVAL if pid is not valid (includes pid <= 0). | ||
| if (pid as i32) <= 0 { | ||
| return Err(AxError::InvalidInput); | ||
| } | ||
|
|
||
| let fd = if flags.contains(PidFdFlags::THREAD) { | ||
| PidFd::new_thread(get_task(pid)?.as_thread()) | ||
| PidFd::new_thread(get_task(pid)?.as_thread(), pid) | ||
| } else { | ||
| // Without PIDFD_THREAD the target must be a thread-group leader. | ||
| if let Ok(task) = get_task(pid) | ||
| && task.as_thread().proc_data.proc.pid() != pid | ||
| { | ||
| return Err(AxError::NotFound); | ||
| } | ||
| PidFd::new_process(&get_process_data(pid)?) | ||
| }; | ||
| if flags.contains(PidFdFlags::NONBLOCK) { | ||
|
|
@@ -49,16 +92,28 @@ pub fn sys_pidfd_getfd(pidfd: i32, target_fd: i32, flags: u32) -> AxResult<isize | |
|
|
||
| let pidfd = PidFd::from_fd(pidfd)?; | ||
| let proc_data = pidfd.process_data()?; | ||
| check_kill_permission(proc_data.proc.pid())?; | ||
| FD_TABLE | ||
| .scope(&proc_data.scope.read()) | ||
| .read() | ||
| .get(target_fd as usize) | ||
| .ok_or(AxError::BadFileDescriptor) | ||
| .and_then(|fd| { | ||
| let fd = add_file_like(fd.inner.clone(), true)?; | ||
| Ok(fd as isize) | ||
| }) | ||
| let curr_proc_data = current().as_thread().proc_data.clone(); | ||
| let is_current = Arc::ptr_eq(&proc_data, &curr_proc_data); | ||
| if !is_current { | ||
| // Linux __pidfd_fget() uses ptrace_may_access(PTRACE_MODE_ATTACH_REALCREDS). | ||
| // Until Starry has that, require at least kill-style credentials on the target. | ||
| check_kill_permission(proc_data.proc.pid())?; | ||
| } | ||
| let fd_entry = if is_current { | ||
| // Use the live fd table for the current process. `proc_data.scope` is only | ||
| // refreshed on clone/dup paths; syscalls like pipe() update ActiveScope only. | ||
| FD_TABLE.read().get(target_fd as usize).cloned() | ||
| } else { | ||
| FD_TABLE | ||
| .scope(&proc_data.scope.read()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 旧代码在 fd 查找前调用了 Linux 建议在后续 PR 中至少恢复 |
||
| .read() | ||
| .get(target_fd as usize) | ||
| .cloned() | ||
| }; | ||
| fd_entry.ok_or(AxError::BadFileDescriptor).and_then(|fd| { | ||
| let fd = add_file_like(fd.inner.clone(), true)?; | ||
| Ok(fd as isize) | ||
| }) | ||
| } | ||
|
|
||
| pub fn sys_pidfd_send_signal( | ||
|
|
@@ -67,19 +122,60 @@ pub fn sys_pidfd_send_signal( | |
| sig: *mut SignalInfo, | ||
| flags: u32, | ||
| ) -> AxResult<isize> { | ||
| if flags != 0 { | ||
| let flags = PidFdSignalFlags::from_bits(flags).ok_or(AxError::InvalidInput)?; | ||
| if flags.bits().count_ones() > 1 { | ||
| return Err(AxError::InvalidInput); | ||
| } | ||
|
|
||
| let pidfd = PidFd::from_fd(pidfd)?; | ||
| let pid = pidfd.process_data()?.proc.pid(); | ||
| check_kill_permission(pid)?; | ||
| let pidfd_obj = PidFd::from_fd(pidfd)?; | ||
| let proc_data = pidfd_obj.process_data()?; | ||
| let target_pid = proc_data.proc.pid(); | ||
|
|
||
| let sig = if sig.is_null() { | ||
| make_siginfo(signo, SI_USER as _)? | ||
| let scope = if flags.contains(PidFdSignalFlags::THREAD) | ||
| || (flags.is_empty() && pidfd_obj.is_thread()) | ||
| { | ||
| PidFdSignalScope::Thread | ||
| } else if flags.contains(PidFdSignalFlags::PROCESS_GROUP) { | ||
| PidFdSignalScope::ProcessGroup | ||
| } else { | ||
| make_queue_signal_info(pid, signo, sig)? | ||
| PidFdSignalScope::ThreadGroup | ||
| }; | ||
| send_signal_to_process(pid, sig)?; | ||
|
|
||
| let kinfo = if signo == 0 { | ||
| None | ||
| } else if sig.is_null() { | ||
| let signo = parse_signo(signo)?; | ||
| Some(make_pidfd_siginfo(signo, scope)) | ||
| } else { | ||
| let signo_parsed = parse_signo(signo)?; | ||
| let info = unsafe { sig.vm_read_uninit()?.assume_init() }; | ||
| if info.signo() != signo_parsed { | ||
| return Err(AxError::InvalidInput); | ||
| } | ||
| if current().as_thread().proc_data.proc.pid() != target_pid | ||
| && (info.code() >= 0 || info.code() == SI_TKILL) | ||
| { | ||
| return Err(AxError::OperationNotPermitted); | ||
| } | ||
| Some(info) | ||
| }; | ||
|
|
||
| match scope { | ||
| PidFdSignalScope::Thread => { | ||
| let tid = pidfd_obj.tid().ok_or(AxError::InvalidInput)?; | ||
| check_kill_permission(tid)?; | ||
| send_signal_to_thread(Some(target_pid), tid, kinfo)?; | ||
| } | ||
| PidFdSignalScope::ThreadGroup => { | ||
| check_kill_permission(target_pid)?; | ||
| send_signal_to_process(target_pid, kinfo)?; | ||
| } | ||
| PidFdSignalScope::ProcessGroup => { | ||
| let pgid = proc_data.proc.group().pgid(); | ||
| check_kill_permission(pgid)?; | ||
| send_signal_to_process_group(pgid, kinfo)?; | ||
| } | ||
| } | ||
|
|
||
| Ok(0) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #pragma once | ||
|
|
||
| /* 必须在最前面定义,确保 pipe2/gettid 等可用 */ | ||
| #ifndef _GNU_SOURCE | ||
| #define _GNU_SOURCE | ||
| #endif | ||
|
|
||
| /* | ||
| * StarryOS Syscall Test Framework | ||
| * | ||
| * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 | ||
| * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 | ||
| * | ||
| * 用法: | ||
| * TEST_START("测试名"); | ||
| * CHECK(call == expected, "描述"); | ||
| * CHECK_ERR(call, EBADF, "描述"); | ||
| * TEST_DONE(); | ||
| */ | ||
|
|
||
| #include <errno.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| static int __pass = 0; | ||
| static int __fail = 0; | ||
|
|
||
| /* ---- 核心: 带文件名+行号的检查宏 ---- */ | ||
|
|
||
| /* 检查条件为真 */ | ||
| #define CHECK(cond, msg) do { \ | ||
| if (cond) { \ | ||
| printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ | ||
| __pass++; \ | ||
| } else { \ | ||
| printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ | ||
| __FILE__, __LINE__, msg, errno, strerror(errno)); \ | ||
| __fail++; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* 检查 syscall 返回特定值 */ | ||
| #define CHECK_RET(call, expected, msg) do { \ | ||
| errno = 0; \ | ||
| long _r = (long)(call); \ | ||
| long _e = (long)(expected); \ | ||
| if (_r == _e) { \ | ||
| printf(" PASS | %s:%d | %s (ret=%ld)\n", \ | ||
| __FILE__, __LINE__, msg, _r); \ | ||
| __pass++; \ | ||
| } else { \ | ||
| printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ | ||
| __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ | ||
| __fail++; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* 检查 syscall 失败且 errno 符合预期 */ | ||
| #define CHECK_ERR(call, exp_errno, msg) do { \ | ||
| errno = 0; \ | ||
| long _r = (long)(call); \ | ||
| if (_r == -1 && errno == (exp_errno)) { \ | ||
| printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ | ||
| __FILE__, __LINE__, msg, errno); \ | ||
| __pass++; \ | ||
| } else { \ | ||
| printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ | ||
| __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, \ | ||
| strerror(errno)); \ | ||
| __fail++; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* ---- 测试边界 ---- */ | ||
| #define TEST_START(name) \ | ||
| printf("================================================\n"); \ | ||
| printf(" TEST: %s\n", name); \ | ||
| printf(" FILE: %s\n", __FILE__); \ | ||
| printf("================================================\n") | ||
|
|
||
| #define TEST_DONE() \ | ||
| printf("------------------------------------------------\n"); \ | ||
| printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ | ||
| printf("================================================\n\n"); \ | ||
| return __fail > 0 ? 1 : 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| cmake_minimum_required(VERSION 3.20) | ||
| project(test-pidfd-getfd C) | ||
| set(CMAKE_C_STANDARD 11) | ||
| set(CMAKE_C_STANDARD_REQUIRED ON) | ||
| set(CMAKE_C_EXTENSIONS OFF) | ||
| add_executable(test-pidfd-getfd src/main.c) | ||
| target_include_directories(test-pidfd-getfd PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/../../common) | ||
| target_compile_options(test-pidfd-getfd PRIVATE -Wall -Wextra -Werror) | ||
| install(TARGETS test-pidfd-getfd RUNTIME DESTINATION usr/bin/starry-test-suit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当
get_task(pid)找到非 leader 线程时,返回AxError::NotFound→ENOENT。Linux 内核使用
pid_task(pid, PIDTYPE_TGID)查找,非 leader 线程的 tid 无法匹配 TGID,返回ESRCH。虽然语义接近,但 errno 不一致。次要问题,可在后续统一。