The Pedigree Project 0.1
ptrace-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Thread.h"
3#include "pedigree/kernel/process/Uninterruptible.h"
4#include "pedigree/kernel/processor/Processor.h"
5#include "pedigree/kernel/processor/ProcessorInformation.h"
6#include "pedigree/kernel/syscallError.h"
7
8#include <config.h>
9
10#include "PosixSubsystem.h"
11#include "ptrace-syscalls.h"
12#include "trace-state.h"
13
14namespace {
15constexpr unsigned long TraceMe = 0, Continue = 7, GetRegisters = 12, Detach = 17;
16constexpr unsigned long GetSignalInfo = 0x4202, GetRegisterSet = 0x4204;
17constexpr uint32_t GeneralRegisters = 1;
18
19struct LinuxIovec {
20 uint64_t base, length;
21};
22static_assert(sizeof(LinuxIovec) == 16, "Linux amd64 iovec size");
23static_assert(offsetof(LinuxIovec, length) == 8, "Linux amd64 iov_len offset");
24
25class TraceResult {
26 public:
27 TraceResult() : m_Thread(*Processor::information().getCurrentThread()) {}
28 ~TraceResult() {
29 m_Thread.setErrno(m_Error);
30 }
31 long finish(long value) {
32 m_Error = value < 0 ? m_Thread.getErrno() : 0;
33 return value;
34 }
35
36 private:
37 Thread& m_Thread;
38 size_t m_Error = 0;
39};
40
41int statusResult(TraceStatus status) {
42 switch (status) {
43 case TraceStatus::Success:
44 return 0;
45 case TraceStatus::Missing:
46 case TraceStatus::NotStopped:
47 SYSCALL_ERROR(NoSuchProcess);
48 break;
49 case TraceStatus::Denied:
50 SYSCALL_ERROR(NotEnoughPermissions);
51 break;
52 case TraceStatus::Busy:
53 case TraceStatus::Unsupported:
54 SYSCALL_ERROR(OperationNotSupported);
55 break;
56 case TraceStatus::NoMemory:
57 SYSCALL_ERROR(OutOfMemory);
58 break;
59 case TraceStatus::Full:
60 SYSCALL_ERROR(NoMoreProcesses);
61 break;
62 case TraceStatus::Invalid:
63 SYSCALL_ERROR(InvalidArgument);
64 break;
65 }
66 return -1;
67}
68
69bool deferredRequest(unsigned long request) {
70 switch (request) {
71 case 1:
72 case 2:
73 case 3:
74 case 4:
75 case 5:
76 case 6:
77 case 8:
78 case 9:
79 case 13:
80 case 14:
81 case 15:
82 case 16:
83 case 18:
84 case 19:
85 case 24:
86 case 25:
87 case 26:
88 case 30:
89 case 31:
90 case 32:
91 case 33:
92 case 0x4200:
93 case 0x4201:
94 case 0x4203:
95 case 0x4205:
96 case 0x4206:
97 case 0x4207:
98 case 0x4208:
99 case 0x4209:
100 case 0x420a:
101 case 0x420b:
102 case 0x420c:
103 case 0x420d:
104 case 0x420e:
105 case 0x420f:
106 return true;
107 default:
108 return false;
109 }
110}
111
112int copyRegisters(const Amd64UserRegisters& registers, uintptr_t destination, size_t length) {
113 if (length &&
114 !PosixSubsystem::copyToUser(reinterpret_cast<void*>(destination), &registers, length)) {
115 SYSCALL_ERROR(BadAddress);
116 return -1;
117 }
118 return 0;
119}
120} // namespace
121
122long posix_ptrace(unsigned long request, int32_t pid, uintptr_t address, uintptr_t data,
123 bool linuxAbi) {
124 TraceResult result;
125 Uninterruptible lifetime;
126#if !X64
127 SYSCALL_ERROR(OperationNotSupported);
128 return result.finish(-1);
129#else
130 if (!linuxAbi || deferredRequest(request)) {
131 SYSCALL_ERROR(OperationNotSupported);
132 return result.finish(-1);
133 }
134 if (request == TraceMe)
135 return result.finish(statusResult(posix_trace_traceme()));
136 if (request != Continue && request != Detach && request != GetRegisters &&
137 request != GetSignalInfo && request != GetRegisterSet) {
138 SYSCALL_ERROR(IoError);
139 return result.finish(-1);
140 }
141
142 TraceRelationRef relation;
143 if (statusResult(posix_trace_lookup(pid, relation)))
144 return result.finish(-1);
145
146 if (request == Continue || request == Detach) {
147 // Validate the full word before narrowing: -1 and high-bit values are
148 // invalid signals even when their low bits name a supported signal.
149 if (data > 64) {
150 SYSCALL_ERROR(IoError);
151 return result.finish(-1);
152 }
153 return result.finish(statusResult(relation->resume(static_cast<int>(data), request == Detach)));
154 }
155 if (request == GetSignalInfo) {
156 TraceSignalInfo info{};
157 if (statusResult(relation->snapshotSignalInfo(info)))
158 return result.finish(-1);
159 relation.reset();
160 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(data), &info, sizeof(info))) {
161 SYSCALL_ERROR(BadAddress);
162 return result.finish(-1);
163 }
164 return result.finish(0);
165 }
166
167 Amd64UserRegisters registers{};
168 if (statusResult(relation->snapshotRegisters(registers)))
169 return result.finish(-1);
170 relation.reset();
171 if (request == GetRegisters)
172 return result.finish(copyRegisters(registers, data, sizeof(registers)));
173
174 LinuxIovec vector;
175 if (!PosixSubsystem::copyFromUser(&vector, reinterpret_cast<const void*>(data), sizeof(vector))) {
176 SYSCALL_ERROR(BadAddress);
177 return result.finish(-1);
178 }
179 if (static_cast<uint32_t>(address) != GeneralRegisters || vector.length % sizeof(uint64_t)) {
180 SYSCALL_ERROR(InvalidArgument);
181 return result.finish(-1);
182 }
183 if (vector.length > sizeof(registers))
184 vector.length = sizeof(registers);
185 if (copyRegisters(registers, vector.base, vector.length))
186 return result.finish(-1);
187 // The data write can succeed even when this final length store faults.
188 // The input base field is never rewritten, including for a zero-length read.
189 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(data + offsetof(LinuxIovec, length)),
190 &vector.length, sizeof(vector.length))) {
191 SYSCALL_ERROR(BadAddress);
192 return result.finish(-1);
193 }
194 return result.finish(0);
195#endif
196}
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)