Skip to content
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

sys/linux: automatic syscall interface extraction #590

Open
dvyukov opened this issue May 8, 2018 · 17 comments
Open

sys/linux: automatic syscall interface extraction #590

dvyukov opened this issue May 8, 2018 · 17 comments

Comments

@dvyukov
Copy link
Collaborator

dvyukov commented May 8, 2018

This topic pops up periodically. Filing this as tracking bug.

We could use clang static analysis capabilities, or Coccinelle or Smatch. See also difuze which already does something similar using llvm bitcode. And also IMF: Inferred Model-based Fuzzer.
We probably should stick with clang.

Staged implementation plan should probably be:

  1. Start with a tool that is given a header file and generates skeleton of syzkaller descriptions from it (structs with fields, enums as flags).

  2. Extend the tool to read in existing description file and parse corresponding kernel headers and produce warnings about possible mismatches. E.g. descriptions have struct foo with 2 fields, but kernel headers have struct foo with 3 fields (or field sizes/alignment mismatch).

  3. Locate all kernel interfaces (file_operations, sockets, netlink, filesystems, etc).

At this point we have 3 useful functionalities and some infrastructure code to parse kernel headers, extract some info from them and generate syzkaller descriptions from it. Then we can attack:

  1. Automatically extract descriptions. Probably starting from some simple common cases.

  2. Iterate on 4 to extract more descriptions and of higher quality.

  3. A related functionality that may be easy to build on top is collecting set of functions reachable from syscalls. This would be useful to provide meaningful % of covered code in coverage reports. There are lots of functions that are not reachable from syscalls at all (interrupts, soft interrupts, init functions, rcu/timer callbacks, background threads, etc). If we calculate coverage % based on all code, the number will be too pessimistic and not meaningful and it won't be possible to get close to 100%. If we take only reachable functions as the base, then the % must be much more meaningful and optimistic.

Important aspects for interface auto-generation:

  • How will manual and automatic descriptions co-exist? Most likely automated descriptions won't be on par for all subsystems. I can imagine for some subsystems we will use complete generated ones, for some - completely manual (here verification becomes critical), and for some partially manual/partially automated (how that will look like?)
  • Automated descriptions readability and fixability. Lots of automated descriptions that I saw are unreadable mess. When you start digging they turn out to be bad in some way, but discovering that is extremely hard, it should be easy (e.g. literal constant names). And then there should be some way to fix up problems in automated descriptions.
  • Merging descriptions for multiple arches.

For linux the main interfaces are:

  1. syscalls, marked with SYSCALL_DEFINE macros, e.g.:
SYSCALL_DEFINE1(fdatasync, unsigned int, fd)
  1. File operations, marked with struct file_operations, e.g.:
static const struct file_operations timerfd_fops = {
	.release	= timerfd_release,
	.poll		= timerfd_poll,
	.read		= timerfd_read,
	.llseek		= noop_llseek,
	.show_fdinfo	= timerfd_show,
	.unlocked_ioctl	= timerfd_ioctl,
};

There can be associated with anon files returned by syscalls (timer_fd), or mounted to devfs, procfs, binfmt_misc and other special file systems.

  1. Socket operations. Denoted by struct proto_ops, e.g.:
static const struct proto_ops caif_seqpacket_ops = {
	.family = PF_CAIF,
	.owner = THIS_MODULE,
	.release = caif_release,
	.bind = sock_no_bind,
	.connect = caif_connect,
	.socketpair = sock_no_socketpair,
	.accept = sock_no_accept,
	.getname = sock_no_getname,
	.poll = caif_poll,
	.ioctl = sock_no_ioctl,
	.listen = sock_no_listen,
	.shutdown = sock_no_shutdown,
	.setsockopt = setsockopt,
	.getsockopt = sock_no_getsockopt,
	.sendmsg = caif_seqpkt_sendmsg,
	.recvmsg = caif_seqpkt_recvmsg,
	.mmap = sock_no_mmap,
	.sendpage = sock_no_sendpage,
};

Each set of operations is also associated with a specific socket family/type/protocol. In particular we need to understand sockaddr type used for this socket in connect/bind/sendto/etc.

  1. As a special case, ioctl's and set/getsockopt's. These hang off file_operations and proto_ops. These usually contain a switch on commands and we need the switch cases and argument types.

  2. File systems, denoted by struct file_system_type, e.g.:

static struct file_system_type bm_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "binfmt_misc",
	.mount		= bm_mount,
	.kill_sb	= kill_litter_super,
};

Each file system has set of options that is useful to understand, some require an image in particular format.

  1. Netlink, usually denoted by struct nla_policy, e.g.:
static const struct nla_policy cgw_policy[CGW_MAX+1] = {
	[CGW_MOD_AND]	= { .len = sizeof(struct cgw_frame_mod) },
	[CGW_MOD_OR]	= { .len = sizeof(struct cgw_frame_mod) },
	[CGW_MOD_XOR]	= { .len = sizeof(struct cgw_frame_mod) },
	[CGW_MOD_SET]	= { .len = sizeof(struct cgw_frame_mod) },
	[CGW_CS_XOR]	= { .len = sizeof(struct cgw_csum_xor) },
	[CGW_CS_CRC8]	= { .len = sizeof(struct cgw_csum_crc8) },
	[CGW_SRC_IF]	= { .type = NLA_U32 },
	[CGW_DST_IF]	= { .type = NLA_U32 },
	[CGW_FILTER]	= { .len = sizeof(struct can_filter) },
	[CGW_LIM_HOPS]	= { .type = NLA_U8 },
	[CGW_MOD_UID]	= { .type = NLA_U32 },
};

But some messages may also have no policy. There are 3 main levels that I know of: netlink as-is, rtnl, genetlink. All are useful to support.

  1. Netfilter, denoted by struct xt_target and struct xt_match, e.g.:
static struct xt_match xt_osf_match = {
	.name 		= "osf",
	.revision	= 0,
	.family		= NFPROTO_IPV4,
	.proto		= IPPROTO_TCP,
	.hooks      	= (1 << NF_INET_LOCAL_IN) |
				(1 << NF_INET_PRE_ROUTING) |
				(1 << NF_INET_FORWARD),
	.match 		= xt_osf_match_packet,
	.matchsize	= sizeof(struct xt_osf_info),
	.me		= THIS_MODULE,
};

One potential improvement: detect unused/reserved/padding fields (esp in structs, but may be direct syscall args too). These either appear unused in code, or only checked against 0 (to ensure reserved fields have 0 values). We should use const[0] in descriptions for these. Need to be careful to not confuse them with "bool" flags that are also only compared with 0.

@mspecter
Copy link
Contributor

mspecter commented Nov 20, 2018

The Android Security research team is really interested in getting this feature off the ground. I'm going to start hacking on a tool that starts doing some of this using clang (so as to avoid dependence on other projects).

In particular, we're going to start with # 1 as outlined above, specifically targeting Android source as a starting target, but attempt to build-in flexibility later for adding other operating systems. This particularly impacts target # 3 above, since how file operations, sockets, ioctls, etc are all done are definitely going to depend heavily on the OS.

@dvyukov
Copy link
Collaborator Author

dvyukov commented Nov 21, 2018

@mspecter This is great!
Maybe a bit late to point out, but we have this rough prototype:
https://github.com/google/syzkaller/blob/master/tools/syz-declextract/syz-declextract.cpp
If you did something with clang before, then it's probably of no use. But otherwise can be used to get things rolling.

Yes, it would be nice to separate common code and linux-specific code form day one. Ideally, OS-specific info is provided as some set of declarative rules. Whole syzkaller used to be heavily linux-specific throughout, and then it was painful to de-linuxify the codebase.

@dvyukov
Copy link
Collaborator Author

dvyukov commented Nov 21, 2018

As soon as you have something that converts a C struct to a basic syzkaller form, we can start merging.

I think it's important to figure out and agree on user interfaces (how it will look for end users).

For step 1, I am thinking about something along the lines of:

syz-something -sourcedir=/path/to/linux/checkout -header=include/linux/fs/.h -includes=comma-separated-list-of-other-headers-to-include

and this will print syzkaller descriptions from the header to stdout.
We will need -includes flag because lots of kernel headers are not self-contained and require including other headers before they can be parsed.
Any other options?

For step 2, ultimately I would like to run:

make declcheck TARGETOS=linux SOURCEDIR=/path/to/linux/checkout

and that will print set of warnings (we will figure out some story for false positive warnings).

@dvyukov
Copy link
Collaborator Author

dvyukov commented Nov 21, 2018

Another high-level question is how to split code between C++ and Go.
I would prefer that we have most of the logic in Go because we already have machinery to work with syzkaller descriptions in Go, it's much easier to test, it does not depend on clang sources and Go is type-safe and memory-safe language.
We can either have a C++ clang tool that merely produces syzkaller descriptions and then handle the rest in Go by invoking the native tool and parsing it's output; or use Go clang bindings (e.g. https://github.com/go-clang/bootstrap). However, it's unclear how stable and easy-to-use they are and if it will cause more pain in the long run then just writing a native tool.

@mspecter
Copy link
Contributor

Re: code split between Go and C++, I'd love some guidance.

The way I was considering doing this was by writing a clangtool (much like the prototype you linked) to walk the AST since I generally don't trust the Go clang bindings, then leverage the code you already have in pkg/ast/ast.go and pkg/ast/format.go, along with a few Go->C++ bindings to actually create nodes and serialize them out; doing something like an AST->AST conversion.

This has the benefit of relying less on a secondary generator of syzkaller syntax. So, if the project decides to add tokens or change what a token means, this part won't have to be modified as well. The downside appears to be a not-insignificant amount of added complexity. Thoughts?

@mspecter
Copy link
Contributor

Also, I completely agree on the user interface, all of that makes sense. ^_^

@dvyukov
Copy link
Collaborator Author

dvyukov commented Nov 22, 2018

along with a few Go->C++ bindings to actually create nodes and serialize them out; doing something like an AST->AST conversion.

Do you want to call Go from C++, or C++ from Go? It's possible but writing bindings can be painful, also it will be harder to build and test. The pure Go part will build and run trivially an can be tested, so the idea was to localize dependencies on clang to as small binary as possible (not sure if we will be able to tests it on travis CI).
So I was thinking of writing a minimal clangtool that spews syzkaller descriptions (by just printf'ing them, printing is much easier than parsing). And hen have a Go program that parses output with pkg/ast, does transformations, compiles them, formats etc.
However, it seems that the main smartness should happen before clangtool writes descriptions, because otherwise all auxiliary semantic information and relations between things will be lost (can't be represented in syzkaller descriptions). And if clangtool will do all of the hard work, then it's unclear what transformation we still can do on the Go side.
Another option would be for the clangtool to produce raw syzkaller descriptions and some additional auxiliary semantic information and relations in a separate file (say json), and then Go tool will parse both and do some transformations and refining on the descriptions.
But it's hard for me to judge what will work better without knowing what exactly auxiliary information we will have and how complex it is and how complex is post-processing of descriptions.

@dvyukov
Copy link
Collaborator Author

dvyukov commented Dec 30, 2018

I've added descriptions of 7 main Linux kernel interfaces that I know of:
#590 (comment)
Anything else I am missing?
@mspecter

@mspecter
Copy link
Contributor

mspecter commented Jan 2, 2019

I can't currently think of anything you're missing, and that list is incredibly helpful, thanks!

@dvyukov
Copy link
Collaborator Author

dvyukov commented Jun 4, 2019

A related functionality that may be easy to build on top is collecting set of functions reachable from syscalls. This would be useful to provide meaningful % of covered code in coverage reports. There are lots of functions that are not reachable from syscalls at all (interrupts, soft interrupts, init functions, rcu/timer callbacks, background threads, etc). If we calculate coverage % based on all code, the number will be too pessimistic and not meaningful and it won't be possible to get close to 100%. If we take only reachable functions as the base, then the % must be much more meaningful and optimistic.
But we also need to figure out how to integrate this analysis into coverage reports (reports are generated online).

dvyukov added a commit to dvyukov/syzkaller that referenced this issue Dec 17, 2019
syz-check parses vmlinux dwarf, extracts struct descriptions,
compares them with what we have (size, fields, alignment, etc)
and produces .warn files.
This is first raw version, it can be improved in a number of ways.
But it already helped to identify a critical issue google#1542
and shows some wrong struct descriptions.

Update google#590
dvyukov added a commit that referenced this issue Dec 17, 2019
syz-check parses vmlinux dwarf, extracts struct descriptions,
compares them with what we have (size, fields, alignment, etc)
and produces .warn files.
This is first raw version, it can be improved in a number of ways.
But it already helped to identify a critical issue #1542
and shows some wrong struct descriptions.

Update #590
dvyukov added a commit that referenced this issue Dec 18, 2019
dvyukov added a commit that referenced this issue Dec 18, 2019
We assumed that for ConstType alignment is equal to size,
which is perfectly reasonable for normal int8/16/32/64/ptr.
However, padding is also represented by ConstType of arbitrary size,
so if we added 157 bytes of padding that becomes alignment of
the padding field and as the result of the whole struct.
This affects very few structs, but quite radically and quite
important structs.

Discovered thanks to syz-check.

Update #590
dvyukov added a commit that referenced this issue Dec 20, 2019
We used size as alignment, this is very wrong.

Found thanks to syz-check. Update #590
dvyukov added a commit that referenced this issue Dec 20, 2019
Fixes #1542

Found thanks to syz-check. Update #590
dvyukov added a commit that referenced this issue Dec 20, 2019
Sweeping fix of everything up to socket_netlink_route.txt.

Update #590
dvyukov added a commit that referenced this issue Dec 22, 2019
The only remaining part now is dev_video4linux.txt

Update #590
dvyukov added a commit that referenced this issue Dec 22, 2019
dvyukov added a commit that referenced this issue Dec 22, 2019
Also rename some netfilter types to eliminate massive amounts of template warnings.

Update #590
dvyukov added a commit that referenced this issue Dec 22, 2019
dvyukov added a commit that referenced this issue Dec 23, 2019
Turns out int64 alignment is 4 on 386...
But on arm it's still 8.

Another amusing finding thanks to syz-check.

Update #590
dvyukov added a commit that referenced this issue Dec 23, 2019
Lots of interesting findings...
Especially 2 byte uid/gid/pid.

Update #590
dvyukov added a commit to dvyukov/syzkaller that referenced this issue Jan 17, 2020
They mostly duplicate the warnings we already have for amd64/386.
But uncovered few very interesting local things (e.g. epoll_event
is packed only on amd64, so arm/arm64 layout is wrong, but 386
is correct because int64 alignment is different).

Update google#590
dvyukov added a commit that referenced this issue Jan 18, 2020
They mostly duplicate the warnings we already have for amd64/386.
But uncovered few very interesting local things (e.g. epoll_event
is packed only on amd64, so arm/arm64 layout is wrong, but 386
is correct because int64 alignment is different).

Update #590
dvyukov added a commit that referenced this issue Jan 18, 2020
dvyukov added a commit that referenced this issue Jan 22, 2020
Overall idea of netlink checking.
Currnetly we check netlink policies for common detectable mistakes.
First, we detect what looks like a netlink policy in our descriptions
(these are structs/unions only with nlattr/nlnext/nlnetw fields).
Then we find corresponding symbols (offset/size) in vmlinux using nm.
Then we read elf headers and locate where these symbols are in the rodata section.
Then read in the symbol data, which is an array of nla_policy structs.
These structs allow to easily figure out type/size of attributes.
Finally we compare our descriptions with the kernel policy description.

Update #590
dvyukov added a commit that referenced this issue Jan 22, 2020
dvyukov added a commit that referenced this issue Jan 22, 2020
1. Match policies that has a _suffix in our descriptions
(we frequently do this to improve precision or avoid dup names).
2. Rename policies in descriptions to match kernel names.
3. Match policy if there are several such names in kernel.
4. Recognize policies with helper sub-policies.

Update #590
dvyukov added a commit that referenced this issue Jan 22, 2020
As far as I understand most subsystems don't care about
the nest flag, but some do. But marking them as nest
won't harm (?). Let's mark all of them.

Caught several cases where should have been used array[policy]
but used just policy.

Update #590
dvyukov added a commit that referenced this issue Jan 22, 2020
dvyukov added a commit that referenced this issue Jan 22, 2020
dvyukov added a commit that referenced this issue Jan 23, 2020
Handle NLA_BITFIELD32.
Match string attribtues better.
Calculate and check min size for varlen structs.
Fix NLA_UNSPEC size check.
Fix some things in descriptions.

Update #590
dvyukov added a commit that referenced this issue Jan 23, 2020
Stop at the fist varlen field, but check the preceeding ones.
Frequently the varlen array is the last field,
so we should get good checking for these cases.

Update #590
dvyukov added a commit that referenced this issue Jan 28, 2020
Thanks to syz-check for catching this.

Update #590
@dvyukov
Copy link
Collaborator Author

dvyukov commented Jul 29, 2020

One thing that may be useful and possible with smarter code analysis: detecting unused/reserved/padding fields (esp in structs, but may be direct syscall args too). These either appear unused in code, or only checked against 0. We should use const[0] in descriptions for these.

@dvyukov
Copy link
Collaborator Author

dvyukov commented Jul 3, 2021

FTR here is an interface extraction utility for NetBSD, can extract ioctl definitions:
https://github.com/ais2397/sys2syz

@dvyukov
Copy link
Collaborator Author

dvyukov commented Nov 16, 2021

@dvyukov
Copy link
Collaborator Author

dvyukov commented Sep 13, 2022

For netlink we could consider extracting info from ynl project once it's merged upstream:
https://github.com/kuba-moo/ynl/blob/main/Documentation/netlink/specs/fou.yaml
It contains machine-readable descriptions of netlink protocols.
However, it needs to cover more protocols and needs to be extended with semantic info for attributes (ip addr, ifindex, fd, device name, etc).

@dvyukov
Copy link
Collaborator Author

dvyukov commented Jan 10, 2023

FTR another paper on automatic interface extraction:
KSG: Augmenting Kernel Fuzzing with System Call Specification Generation

@izzeem
Copy link

izzeem commented Sep 7, 2023

https://github.com/seclab-ucr/SyzDescribe another paper which claims to be better than KSG

@dvyukov
Copy link
Collaborator Author

dvyukov commented Jan 18, 2024

One interesting addition to this is analysis of what interfaces are reachable on with different privileges (non-privileged, requires userns, root-only). This info can also be crossed with coverage reports to see e.g. what unpriv interfaces are not covered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants