Initial import
This commit is contained in:
commit
3f6517aae2
1
GetRuntimeAddresses/.gitignore
vendored
Normal file
1
GetRuntimeAddresses/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
GetRuntimeAddresses
|
94
GetRuntimeAddresses/ebpf/ebpf.go
Normal file
94
GetRuntimeAddresses/ebpf/ebpf.go
Normal file
@ -0,0 +1,94 @@
|
||||
package ebpf
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/bpf.h>
|
||||
|
||||
// See the bpf man page for details on the following functions
|
||||
|
||||
static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
|
||||
{
|
||||
return syscall(__NR_bpf, cmd, attr, size);
|
||||
}
|
||||
|
||||
static int bpf_create_map(
|
||||
enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size,
|
||||
unsigned int max_entries)
|
||||
{
|
||||
union bpf_attr attr = {
|
||||
.map_type = map_type,
|
||||
.key_size = key_size,
|
||||
.value_size = value_size,
|
||||
.max_entries = max_entries};
|
||||
|
||||
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
int bpf_get_next_key(int fd, const void *key, void *next_key)
|
||||
{
|
||||
union bpf_attr attr = {
|
||||
.map_fd = fd,
|
||||
.key = (__u64) (unsigned long) key,
|
||||
.next_key = (__u64) (unsigned long) next_key};
|
||||
|
||||
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
|
||||
int bpf_lookup_elem(int fd, const void *key, void *value)
|
||||
{
|
||||
union bpf_attr attr = {
|
||||
.map_fd = fd,
|
||||
.key = (__u64) (unsigned long) key,
|
||||
.value = (__u64) (unsigned long) value,
|
||||
};
|
||||
|
||||
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// CreateMap creates an eBPF map from int->uint64. The file descriptor of the
|
||||
// created map is returned.
|
||||
func CreateMap() (int, error) {
|
||||
mapFD := C.bpf_create_map(C.BPF_MAP_TYPE_HASH, 4, 8, 64)
|
||||
return int(mapFD), nil
|
||||
}
|
||||
|
||||
// GetMap gets the key/value pairs from the specified eBPF map as a Go map
|
||||
func GetMap(mapFD int) (map[int]uint64, error) {
|
||||
retMap := make(map[int]uint64)
|
||||
|
||||
mapFDC := C.int(mapFD)
|
||||
keyC := C.int(0)
|
||||
nextKeyC := C.int(0)
|
||||
valC := C.uint64_t(0)
|
||||
|
||||
for {
|
||||
r, errno := C.bpf_get_next_key(mapFDC, unsafe.Pointer(&keyC), unsafe.Pointer(&nextKeyC))
|
||||
if r == -1 {
|
||||
if errno == syscall.ENOENT {
|
||||
// The provided key was the last element. We're done iterating.
|
||||
return retMap, nil
|
||||
}
|
||||
return nil, fmt.Errorf("bpf_get_next_key failed with errno %d", errno)
|
||||
}
|
||||
|
||||
r = C.bpf_lookup_elem(mapFDC, unsafe.Pointer(&nextKeyC), unsafe.Pointer(&valC))
|
||||
if r == -1 {
|
||||
return nil, fmt.Errorf("bpf_lookup_elem failed")
|
||||
}
|
||||
retMap[int(nextKeyC)] = uint64(valC)
|
||||
|
||||
keyC = nextKeyC
|
||||
}
|
||||
}
|
34
GetRuntimeAddresses/main.go
Normal file
34
GetRuntimeAddresses/main.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/ebpf"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mapFD, err := ebpf.CreateMap()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create eBPF map: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Created eBPF map (FD: %d)\n", mapFD)
|
||||
|
||||
//
|
||||
// Solution to your tasks goes here
|
||||
//
|
||||
|
||||
mapContents, err := ebpf.GetMap(mapFD)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get the map contents: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Printing contents of map %d\n", mapFD)
|
||||
for k, v := range mapContents {
|
||||
fmt.Printf("\t%d -> 0x%x\n", k, v)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
110
README.md
Normal file
110
README.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Overview
|
||||
|
||||
The goal of this exercise is to construct a Go program that populates an eBPF
|
||||
map with the address in memory of the `_PyRuntime` global variable for all live
|
||||
Python 3.7 processes. This variable is typically found within `libpython`, which
|
||||
is dynamically loaded. The target system can be assumed to be a 64-bit Linux OS
|
||||
running a 5.x kernel.
|
||||
|
||||
As an example, say there are two live processes that have mapped
|
||||
`libpython3.7m.so.1.0` (on your OS `libpython` may have a different name) into
|
||||
their address spaces, with process IDs 100 and 200. In process 100 the library
|
||||
is mapped at address 0x10000, and in process 200 the library is mapped at
|
||||
address 0x20000. Furthermore, assume that `_PyRuntime` is at offset 256
|
||||
within `libpython3.7m.so.1.0`.
|
||||
|
||||
Your solution should populate an eBPF map with the following two entries: (100
|
||||
-> 0x10256), (200 -> 0x20256).
|
||||
|
||||
### Your Solution
|
||||
|
||||
Treat your solution as if this was code you would have to live with for a while,
|
||||
rather than just a throw-away proof-of-concept. i.e. ideally it should be
|
||||
modular, with a reasonable breakdown of functionality into packages and functions
|
||||
as required, documented and formatted as per the Go standards, with any tests as
|
||||
may be necessary (aiming for full test coverage isn't necessary. Use your
|
||||
judgement as to what may need a test and what may not).
|
||||
|
||||
When you have completed your solution, create a pull request for it to the master
|
||||
branch.
|
||||
|
||||
## Task 1
|
||||
|
||||
Enumerate all live processes that contain a Python 3.7 interpreter. Note that
|
||||
this includes both Python REPL processes that have Python as their main binary,
|
||||
and other processes that have libpython.*.so loaded in their address space.
|
||||
|
||||
## Task 2
|
||||
|
||||
For all processes containing a Python interpreter determine the address of the
|
||||
`_PyRuntime` global variable within each process. In Python 3.7 this variable is
|
||||
in the dynamic symbols of the Python library and the symbol information will be
|
||||
available regardless of whether or not debug symbols are available. Note that
|
||||
due to address space layout randomization the actual address of the variable
|
||||
within each process will differ.
|
||||
|
||||
## Task 3
|
||||
|
||||
For all live processes containing a Python interpreter, add a mapping from
|
||||
process ID to the address of `_PyRuntime` to an eBPF map. The skeleton solution
|
||||
provides an `ebpf` package with a function for creating a map, as well as a
|
||||
function for reading a map. You need to add a function for writing to the map.
|
||||
|
||||
## Getting Started
|
||||
|
||||
You will need to following in order to complete the exercise:
|
||||
|
||||
1. A functioning Go installation. See https://golang.org for information on how
|
||||
to get started with this if you have not already done so.
|
||||
|
||||
2. A functioning Python 3.7 install. Run `python3 --version` to see what the
|
||||
default installed version of Python is on your system. It is also necessary
|
||||
for your Python install to be dynamically linked against `libpython`. To
|
||||
check if this is the case run `ldd /path/to/python3.7` and check for a
|
||||
`libpython` entry in the output. If there is no such entry the easiest
|
||||
solution is to download the Python 3.7 source code from
|
||||
https://www.python.org/downloads/release/python-376/ and compile/install it
|
||||
via ``./configure --prefix=`pwd`/install --enable-shared && make && make
|
||||
install``. Once that completes you will have a Python interpreter at
|
||||
`./install/bin/python3.7` that you can use.
|
||||
|
||||
3. A running Linux kernel with eBPF enabled. It is enabled by default on most
|
||||
modern kernel configurations. To check if this is the case for yours, search
|
||||
the config of your running kernel for `CONFIG_BPF=y`. On Fedora this can be
|
||||
done via `cat /boot/config-$(uname -r)| grep -i "CONFIG_BPF=y"`.
|
||||
|
||||
Clone this repository into `$GOPATH/src/github.com/optimyze-interviews`. A
|
||||
skeleton Go program is provided in the `GetRuntimeAddresses` sub-directory. From
|
||||
within `GetRuntimeAddresses` you can build the program via `go build`. This will
|
||||
create a binary called `./GetRuntimeAddresses`. In its current state it will
|
||||
simply create an eBPF map, attempt to print the contents of this map, and then
|
||||
exit. The output would look something like the following:
|
||||
|
||||
```
|
||||
$ ./GetRuntimeAddresses
|
||||
Created eBPF map (FD: 3)
|
||||
Printing contents of map 3
|
||||
```
|
||||
|
||||
Finally, a Python script called `runforever.py` is also provided. When run via
|
||||
`python3.7 ./runforever.py` this script simply prints the ID of the Python process and
|
||||
enters an infinite loop. You can run a few of these simulataneously to test your
|
||||
solution, if you wish.
|
||||
|
||||
# Supporting Material
|
||||
|
||||
## Golang
|
||||
|
||||
* https://golang.org/
|
||||
* https://golang.org/doc/
|
||||
* https://tour.golang.org/welcome/1
|
||||
|
||||
# ELF Symbols
|
||||
|
||||
* https://www.intezer.com/executable-linkable-format-101-part-2-symbols/
|
||||
|
||||
## eBPF
|
||||
|
||||
* https://lwn.net/Articles/740157/
|
||||
* http://man7.org/linux/man-pages/man2/bpf.2.html
|
||||
|
7
runforever.py
Executable file
7
runforever.py
Executable file
@ -0,0 +1,7 @@
|
||||
import time
|
||||
import os
|
||||
|
||||
print("Process ID: {}".format(os.getpid()))
|
||||
print("Entering infinite loop ...")
|
||||
while 1:
|
||||
time.sleep(2)
|
Loading…
Reference in New Issue
Block a user