Skip to content

Commit 26697ce

Browse files
committed
implement lstat
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
1 parent 800953d commit 26697ce

File tree

3 files changed

+180
-57
lines changed

3 files changed

+180
-57
lines changed

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
`subuidless` is an implementaion of OCI Seccomp Receiver for running Rootless Containers without `/etc/subuid` and `/etc/subgid`.
44

55
`subuidlesss` emulates ID-related system calls using Seccomp User Notification and XAttrs.
6-
Unlike ptrace implementatins of similar projects such as [runROOTLESS (PRoot)](https://github.com/rootless-containers/runrootless) and [remainroot](https://github.com/cyphar/remainroot), `subuidless` can minimize the overhead of system call hooking.
6+
7+
Unlike similar projects such as [runROOTLESS (PRoot)](https://github.com/rootless-containers/runrootless) and [remainroot](https://github.com/cyphar/remainroot), `subuidless` can minimize the overhead of system call hooking, as `subuidless` does not use ptrace.
78

89
## Status
910

@@ -64,20 +65,25 @@ $ RUN_OCI_SECCOMP_RECEIVER=~/.subuidless.sock unshare -r crun run -b ./test foo
6465
0 1001 1
6566
/ # touch foo
6667
/ # chown 42:42 foo
68+
/ # ls -ln foo
69+
-rw-r--r-- 1 42 42 0 Jul 29 12:06 foo
6770
```
6871

6972
Make sure that the `chown` command succeeds without `Invalid argument` error, even though no subuid is configured in the `uid_map` file.
70-
The UID ang GID are recorded to [the `user.rootlesscontainers` xattr](https://github.com/rootless-containers/proto) of the target file.
7173

72-
> *FIXME*:
73-
> The chowned value are not shown in `ls -l` currently. Will be shown after the implementaion of stat syscalls.
74-
> Use `getfattr -d -e hex -m user.rootlesscontainers` to inspect the xattr value.
74+
The UID ang GID are recorded to [the `user.rootlesscontainers` xattr](https://github.com/rootless-containers/proto) of the target file.
7575

7676
## Hooked system calls
77-
To be documented, see `SCMP_ACT_NOTIFY` entries in `./test/config.json`.
77+
- [X] `chown`
78+
- [ ] `fchown`
79+
- [ ] `fchownat`
80+
- [ ] `lchown`
7881

79-
<!--
80-
TODO: Syscalls to be captured:
82+
- [X] `lstat`
83+
- ...
84+
85+
TODO:
86+
```
8187
https://github.com/rootless-containers/PRoot/blob/081bb63955eb4378e53cf4d0eb0ed0d3222bf66e/src/extension/fake_id0/fake_id0.c#L141-L205
8288
https://github.com/cyphar/remainroot/blob/master/src/ptrace/generic-shims.c
83-
-->
89+
```

main.c

Lines changed: 164 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <stdio.h>
99
#include <stdlib.h>
1010
#include <sys/socket.h>
11+
#include <sys/stat.h>
1112
#include <sys/types.h>
1213
#include <sys/uio.h>
1314
#include <sys/un.h>
@@ -51,13 +52,25 @@ static int recvfd(int sock) {
5152
return fd;
5253
}
5354

54-
static int read_proc_mem_string(char **out, pid_t pid, off_t off,
55-
size_t max_len) {
56-
const size_t buf_len = max_len + 1;
57-
char *buf = malloc(buf_len);
55+
static ssize_t write_proc_mem(void *in, pid_t pid, off_t off, size_t buf_len) {
5856
struct iovec local[1];
5957
struct iovec remote[1];
60-
ssize_t nread;
58+
ssize_t nwrite = -1;
59+
local[0].iov_base = in;
60+
local[0].iov_len = buf_len;
61+
remote[0].iov_base = (void *)off;
62+
remote[0].iov_len = buf_len;
63+
if ((nwrite = process_vm_writev(pid, local, 1, remote, 1, 0)) < 0) {
64+
perror("process_vm_readv");
65+
}
66+
return nwrite;
67+
}
68+
69+
static ssize_t read_proc_mem(void **out, pid_t pid, off_t off, size_t buf_len) {
70+
void *buf = malloc(buf_len);
71+
struct iovec local[1];
72+
struct iovec remote[1];
73+
ssize_t nread = -1;
6174
local[0].iov_base = buf;
6275
local[0].iov_len = buf_len;
6376
remote[0].iov_base = (void *)off;
@@ -68,45 +81,23 @@ static int read_proc_mem_string(char **out, pid_t pid, off_t off,
6881
*out = NULL;
6982
return nread;
7083
}
71-
buf[max_len] = '\0';
7284
*out = buf;
73-
return 0;
85+
return nread;
7486
}
7587

76-
#define USER_ROOTLESSCONTAINERS_XATTR "user.rootlesscontainers"
77-
78-
static int set_user_rootlesscontainers_xattr_chdired(const char *pathname,
79-
uid_t uid, gid_t gid) {
80-
uint8_t *buf = NULL;
81-
size_t sz = 0;
82-
int rc = -1;
83-
Rootlesscontainers__Resource msg;
84-
if (uid == 0 && gid == 0) {
85-
printf("DEBUG: removing %s xattr on \"%s\" in the PID cwd\n",
86-
USER_ROOTLESSCONTAINERS_XATTR, pathname);
87-
if ((rc = removexattr(pathname, USER_ROOTLESSCONTAINERS_XATTR)) < 0) {
88-
perror("removexattr");
89-
}
88+
static ssize_t read_proc_mem_string(char **out, pid_t pid, off_t off,
89+
size_t max_len) {
90+
ssize_t rc = read_proc_mem((void **)out, pid, off, max_len + 1);
91+
if (rc <= 0) {
9092
return rc;
9193
}
92-
rootlesscontainers__resource__init(&msg);
93-
msg.uid = uid;
94-
msg.gid = gid;
95-
sz = rootlesscontainers__resource__get_packed_size(&msg);
96-
buf = malloc(sz);
97-
rootlesscontainers__resource__pack(&msg, buf);
98-
printf("DEBUG: setting %s xattr (%ld bytes) on \"%s\" in the PID cwd\n",
99-
USER_ROOTLESSCONTAINERS_XATTR, sz, pathname);
100-
if ((rc = setxattr(pathname, USER_ROOTLESSCONTAINERS_XATTR, buf, sz, 0)) <
101-
0) {
102-
perror("setxattr");
103-
}
104-
free(buf);
105-
return rc;
94+
(*out)[rc - 1] = '\0';
95+
return strlen(*out);
10696
}
10797

108-
static int set_user_rootlesscontainers_xattr(pid_t pid, const char *pathname,
109-
uid_t uid, gid_t gid) {
98+
typedef int (*wpc_callback_t)(void *opaque);
99+
100+
static int with_pid_cwd(pid_t pid, wpc_callback_t cb, void *opaque) {
110101
char proc_pid_cwd[32];
111102
char *wd = NULL;
112103
int rc = -1;
@@ -120,7 +111,7 @@ static int set_user_rootlesscontainers_xattr(pid_t pid, const char *pathname,
120111
free(wd);
121112
return rc;
122113
}
123-
rc = set_user_rootlesscontainers_xattr_chdired(pathname, uid, gid);
114+
rc = cb(opaque);
124115
if (chdir(wd) < 0) {
125116
perror("chdir");
126117
fprintf(stderr, "can't chdir back to the previous wd \"%s\", aborting\n",
@@ -130,7 +121,53 @@ static int set_user_rootlesscontainers_xattr(pid_t pid, const char *pathname,
130121
return -1;
131122
}
132123
free(wd);
133-
return 0;
124+
return rc;
125+
}
126+
127+
#define XA_USER_ROOTLESSCONTAINERS "user.rootlesscontainers"
128+
129+
struct x_chown_arg {
130+
const char *pathname;
131+
uid_t uid;
132+
gid_t gid;
133+
};
134+
135+
static int x_chown_cb(void *opaque) {
136+
uint8_t *buf = NULL;
137+
size_t sz = 0;
138+
int rc = -1;
139+
struct x_chown_arg *arg = opaque;
140+
if (arg->uid == 0 && arg->gid == 0) {
141+
printf("DEBUG: removing %s xattr on \"%s\" in the PID cwd\n",
142+
XA_USER_ROOTLESSCONTAINERS, arg->pathname);
143+
if ((rc = lremovexattr(arg->pathname, XA_USER_ROOTLESSCONTAINERS)) < 0) {
144+
perror("lremovexattr");
145+
}
146+
return rc;
147+
}
148+
Rootlesscontainers__Resource msg;
149+
rootlesscontainers__resource__init(&msg);
150+
msg.uid = arg->uid;
151+
msg.gid = arg->gid;
152+
sz = rootlesscontainers__resource__get_packed_size(&msg);
153+
buf = malloc(sz);
154+
rootlesscontainers__resource__pack(&msg, buf);
155+
printf("DEBUG: setting %s xattr (%ld bytes) on \"%s\" in the PID cwd\n",
156+
XA_USER_ROOTLESSCONTAINERS, sz, arg->pathname);
157+
if ((rc = lsetxattr(arg->pathname, XA_USER_ROOTLESSCONTAINERS, buf, sz, 0)) <
158+
0) {
159+
perror("lsetxattr");
160+
}
161+
free(buf);
162+
return rc;
163+
}
164+
165+
static int x_chown(pid_t pid, const char *pathname, uid_t uid, gid_t gid) {
166+
struct x_chown_arg arg;
167+
arg.pathname = pathname;
168+
arg.uid = uid;
169+
arg.gid = gid;
170+
return with_pid_cwd(pid, x_chown_cb, &arg);
134171
}
135172

136173
static void handle_sys_chown(struct seccomp_notif *req,
@@ -139,16 +176,95 @@ static void handle_sys_chown(struct seccomp_notif *req,
139176
uid_t uid = req->data.args[1];
140177
gid_t gid = req->data.args[2];
141178
read_proc_mem_string(&pathname, req->pid, req->data.args[0], PATH_MAX);
142-
fprintf(stderr, "debug: <<< ID=%llud sys_chown(\"%s\", %d, %d)\n", req->id,
143-
pathname, uid, gid);
144-
if (set_user_rootlesscontainers_xattr(req->pid, pathname, uid, gid) < 0) {
145-
resp->error = -1;
146-
resp->error = -EIO;
179+
int rc;
180+
if ((rc = x_chown(req->pid, pathname, uid, gid)) < 0) {
181+
resp->val = rc;
182+
resp->error = -errno;
183+
if (resp->error == 0) {
184+
resp->error = -EIO;
185+
}
147186
}
148-
fprintf(stderr, "debug: >>> ID=%llud error=%d\n", req->id, resp->error);
149187
free(pathname);
150188
}
151189

190+
struct x_lstat_arg {
191+
const char *pathname;
192+
struct stat *st;
193+
};
194+
195+
static int x_lstat_cb(void *opaque) {
196+
struct x_lstat_arg *arg = opaque;
197+
int rc = lstat(arg->pathname, arg->st);
198+
if (rc < 0) {
199+
return rc;
200+
}
201+
/* convert uid-outside-userns into uid-inside-userns, e.g. 1001 -> 0 */
202+
/* TODO: use /proc/%d/uid_map if extists */
203+
/* TODO: can we call lstat inside userns? */
204+
if (arg->st->st_uid == geteuid()) {
205+
arg->st->st_uid = 0;
206+
}
207+
if (arg->st->st_gid == getegid()) {
208+
arg->st->st_gid = 0;
209+
}
210+
211+
ssize_t xa_sz = lgetxattr(arg->pathname, XA_USER_ROOTLESSCONTAINERS, NULL, 0);
212+
if (xa_sz <= 0) {
213+
/* The xattr is not set */
214+
return rc;
215+
}
216+
void *xa_buf = malloc(xa_sz);
217+
if (lgetxattr(arg->pathname, XA_USER_ROOTLESSCONTAINERS, xa_buf, xa_sz) < 0) {
218+
perror("lgetxattr");
219+
/* ignore error */
220+
return rc;
221+
}
222+
Rootlesscontainers__Resource *msg =
223+
rootlesscontainers__resource__unpack(NULL, xa_sz, xa_buf);
224+
if (msg == NULL) {
225+
fprintf(stderr, "cannot decode %s xattr on \"%s\"\n",
226+
XA_USER_ROOTLESSCONTAINERS, arg->pathname);
227+
/* ignore error */
228+
free(xa_buf);
229+
return rc;
230+
}
231+
if (msg->uid >= 0) {
232+
arg->st->st_uid = msg->uid;
233+
}
234+
if (msg->gid >= 0) {
235+
arg->st->st_gid = msg->gid;
236+
}
237+
free(xa_buf);
238+
rootlesscontainers__resource__free_unpacked(msg, NULL);
239+
return rc;
240+
}
241+
242+
static int x_lstat(pid_t pid, const char *pathname, struct stat *st) {
243+
struct x_lstat_arg arg;
244+
arg.pathname = pathname;
245+
arg.st = st;
246+
return with_pid_cwd(pid, x_lstat_cb, &arg);
247+
}
248+
249+
static void handle_sys_lstat(struct seccomp_notif *req,
250+
struct seccomp_notif_resp *resp) {
251+
char *pathname = NULL;
252+
struct stat *st = NULL;
253+
read_proc_mem_string(&pathname, req->pid, req->data.args[0], PATH_MAX);
254+
read_proc_mem((void **)&st, req->pid, req->data.args[1], sizeof(struct stat));
255+
int rc;
256+
if ((rc = x_lstat(req->pid, pathname, st)) < 0) {
257+
resp->val = rc;
258+
resp->error = -errno;
259+
if (resp->error == 0) {
260+
resp->error = -EIO;
261+
}
262+
}
263+
write_proc_mem(st, req->pid, req->data.args[1], sizeof(struct stat));
264+
free(pathname);
265+
free(st);
266+
}
267+
152268
static void handle_req(struct seccomp_notif *req,
153269
struct seccomp_notif_resp *resp) {
154270
resp->id = req->id;
@@ -157,6 +273,9 @@ static void handle_req(struct seccomp_notif *req,
157273
case __NR_chown:
158274
handle_sys_chown(req, resp);
159275
break;
276+
case __NR_lstat:
277+
handle_sys_lstat(req, resp);
278+
break;
160279
default:
161280
fprintf(stderr, "Unexpected syscall %d, returning -ENOTSUP\n",
162281
req->data.nr);

test/config.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,7 @@
170170
{
171171
"names": [
172172
"chown",
173-
"fchown",
174-
"lchown",
175-
"fchownat"
173+
"lstat"
176174
],
177175
"action": "SCMP_ACT_NOTIFY"
178176
}

0 commit comments

Comments
 (0)