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