Gallopsled / Gallopsled/pwntools

Incorrect prototype of the clone syscall in pwnlib.shellcraft

Open
#2,283 2 comments 0 reactions 0 assignees View on GitHub
backport-required bug shellcode
Dominant language
Python
Stars
13.7k
Forks
1.9k
Avg merge
6d 23h
Merged PRs (30d)
3

Description

Hey,
I noticed that the pwnlib.shellcraft.clone() implementation has the prototype of the glibc wrapper function, and invokes the syscall by this prototype, but should be calling the syscall by its raw prototype.

The clone() system call glibc wrapper function and the actual raw system call have different function prototypes, as mentioned in the [man page](https://man7.org/linux/man-pages/man2/clone.2.html#VERSIONS):

```
/* Prototype for the glibc wrapper function */

#define _GNU_SOURCE
#include

int clone(int (*fn)(void *_Nullable), void *stack, int flags,
void *_Nullable arg, ... /* pid_t *_Nullable parent_tid,
void *_Nullable tls,
pid_t *_Nullable child_tid */ );
...
C library/kernel differences
The raw clone() system call corresponds more closely to fork(2)
in that execution in the child continues from the point of the
call. As such, the fn and arg arguments of the clone() wrapper
function are omitted.
...
The raw system call interface on x86-64 and some other
architectures (including sh, tile, and alpha) is:

long clone(unsigned long flags, void *stack,
int *parent_tid, int *child_tid,
unsigned long tls);

On x86-32, and several other common architectures (including
score, ARM, ARM 64, PA-RISC, arc, Power PC, xtensa, and MIPS),
the order of the last two arguments is reversed:

long clone(unsigned long flags, void *stack,
int *parent_tid, unsigned long tls,
int *child_tid);
...
```

From the documentation of pwnlib.shellcraft.clone():
```python
Signature:
pwnlib.shellcraft.clone(
fn=0,
child_stack=0,
flags=0,
arg=0,
vararg_0=None,
vararg_1=None,
vararg_2=None,
vararg_3=None,
vararg_4=None,
)
```

Simple code snippet and output:
```python
In [15]: print(pwnlib.shellcraft.clone(1,2,3,4))
/* clone(fn=1, child_stack=2, flags=3, arg=4) */
mov x0, #1
mov x1, #2
mov x2, #3
mov x3, #4
/* call clone() */
mov x8, #SYS_clone
svc 0
```

As you can see, in the above case (aarch64), the first argument passed is the fn pointer, when the syscall expects the flags argument. In the same way, the flags argument is passed as the parent_tid, and the fn argument is not even expected by the syscall.
Possible solutions would be to either implement the glibc wrapper function logic, or to edit the function prototype to match the raw syscall, per architecture.

Contributor guide

Open the contributing guide

Research direction

Start with pwnlib.shellcraft.clone() and compare its generated register arguments with the raw clone syscall prototypes in the linked man page for each architecture. Resolve whether the project should preserve wrapper semantics or use raw-syscall semantics, then update the signature and generated behavior accordingly; done when architecture-specific calls match the selected prototype.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, python
Domain
devtools, operating-systems, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.