Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Docs

# Documentation (Sphinx HTML) build + dual publish, adapted from Penguin's
# docs.yaml.
#
# Difference from Penguin: Penguin builds its docs from a live runtime image
# (its Sphinx run introspects live Python plugins), so it triggers via
# `workflow_run` after the container publish. igloo_driver is a pure C kernel
# module with no runtime doc path — the docs are a plain `sphinx-build` of
# docs/ (Doxygen extracts the C API; see docs/conf.py). That build depends only
# on docs/ + src/, NOT on the (heavy, multi-arch) module build, so we trigger
# directly on pushes to main that touch docs, plus manual dispatch. This keeps
# docs decoupled from the module release, matching Penguin's intent.
#
# The deploy_static job is lifted almost verbatim from Penguin: it publishes the
# rendered HTML to BOTH GitHub Pages and a generated-only `docs` branch.
on:
push:
branches: [main]
paths:
- "docs/**"
- "README.md"
- "src/**"
- ".github/workflows/docs.yaml"
workflow_dispatch:

permissions:
contents: write # force-push the rendered HTML to the `docs` branch
pages: write
id-token: write

concurrency:
group: "docs"
cancel-in-progress: false

jobs:
build_docs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Doxygen
run: |
sudo apt-get update
sudo apt-get install -y doxygen

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install documentation dependencies
run: pip install -r docs/requirements.txt

- name: Build HTML docs
run: |
# conf.py runs Doxygen itself, so a bare sphinx-build is
# self-contained. -W turns warnings into errors to keep the docs
# honest (the build is currently warning-clean).
sphinx-build -b html -W --keep-going docs docs/_build/html
tar -czvf docs.tar.gz -C docs/_build/html .

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: html
path: docs.tar.gz

deploy_static:
needs: build_docs
runs-on: ubuntu-latest
permissions:
contents: write # force-push the rendered HTML to the `docs` branch
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: html
- name: Unzip HTML
run: |
mkdir html_content
tar -xvf docs.tar.gz -C html_content

# Also publish the rendered site to a `docs` branch so it can be cloned
# and served directly (git clone --branch docs --depth 1 …), independent
# of the GitHub Pages artifact deploy below. force_orphan keeps `docs` as a
# single-commit, generated-only branch with no shared history — never
# hand-edit it. peaceiris disables Jekyll (drops a .nojekyll) by default,
# which Sphinx's `_static/` dirs require.
- name: Publish HTML to the docs branch
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./html_content
publish_branch: docs
force_orphan: true

# A re-run does NOT clear artifacts uploaded by a prior attempt.
# upload-pages-artifact always names its artifact "github-pages", so a
# re-run leaves 2+ identically-named artifacts and deploy-pages@v4 aborts
# with "Multiple artifacts named github-pages were unexpectedly found".
# Delete any stale one first so exactly one exists at deploy time.
- name: Delete stale github-pages artifacts from prior attempts
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ids=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts" \
--paginate --jq '.artifacts[] | select(.name=="github-pages") | .id')
for id in $ids; do
echo "Deleting stale github-pages artifact $id"
gh api -X DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${id}" || true
done
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'html_content'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# igloo_driver

**igloo_driver** is the in-guest half of the [IGLOO / Penguin](https://github.com/rehosting/penguin)
firmware rehosting stack — a small, built-in Linux kernel module that turns an
emulated guest kernel into a **programmable kernel debugger**. From a host-side
PANDA / QEMU analysis plugin (Penguin) you can, at runtime:

- **Read and write** kernel and user memory (bytes, strings, pointer arrays,
whole process address spaces);
- **Introspect the OS** — walk processes and read their maps, `argv`, `environ`,
open FDs, registers, and executable paths;
- **Install hooks** — kprobes, uprobes, syscall entry/return hooks, and signal
delivery hooks, each reported back to the host (and optionally able to skip a
syscall or drop a signal);
- **Call kernel functions** via an FFI, resolve symbols with `kallsyms`, and
generate trampolines;
- **Synthesize pseudo-files and devices** — `/proc`, `/sys`, `/dev`, `sysctl`,
anonymous inodes, sockets, and MTD flash — backed by host-side models.

All of this rides on a single cooperative shared-memory protocol, **Portal**,
carried over a tiny per-architecture **hypercall** ABI (~50 operations in all).
The module has no configuration of its own; it is orchestrated entirely from the
host by Penguin.

> **Guest side vs. host side.** igloo_driver runs *inside the guest*. The Python
> API that drives it (`plugins.portal.read_str(...)`, kprobe/uprobe
> registration, pseudo-file models) lives in Penguin. If you want the host API,
> see Penguin's [Portal](https://github.com/rehosting/penguin/blob/main/docs/portal.md),
> [kprobes](https://github.com/rehosting/penguin/blob/main/docs/kprobes.md), and
> [uprobes](https://github.com/rehosting/penguin/blob/main/docs/uprobes.md) docs.

## Documentation

Full documentation — architecture, the Portal operation catalog, the hypercall
ABI, hooks, hyperfs, pseudo-file synthesis, and an auto-extracted C API
reference — is published to **GitHub Pages** and mirrored to the **`docs`
branch** of this repository.

To build the docs locally:

```bash
python3 -m venv .venv && . .venv/bin/activate
pip install -r docs/requirements.txt
# Doxygen must also be on PATH (conf.py runs it to extract the C API):
sphinx-build -b html docs docs/_build/html
# open docs/_build/html/index.html
```

## Building the module

igloo_driver is cross-compiled for ~13 architectures against multiple kernel
versions inside a Docker toolchain container. The wrapper is `build.sh`.

**Prerequisites:** Docker; a toolchain image (default
`rehosting/embedded-toolchains:latest`); and kernel headers as
`local_packages/kernel-devel-all.tar.gz` (published by
[`linux_builder`](https://github.com/rehosting/linux_builder)):

```bash
mkdir -p local_packages
curl -L -o local_packages/kernel-devel-all.tar.gz \
https://github.com/rehosting/linux_builder/releases/latest/download/kernel-devel-all.tar.gz
```

Then build:

```bash
./build.sh # all default targets, versions 4.10 & 6.13
./build.sh --versions "4.10 6.7" \
--targets "armel mipseb mipsel" # a subset
./build.sh --release # stripped modules
./build.sh --help # all options
```

The output is a single archive, `igloo_driver.tar.gz`, containing
`igloo.ko.<target>` per target/version. See
[docs/building.md](docs/building.md) for the full reference.

## Using it with Penguin

Drop `igloo_driver.tar.gz` into Penguin's `local_packages/` before a
`./penguin --build` to test a local driver build, or let Penguin fetch the
pinned release. Pushes to `main` publish a versioned GitHub release of the
built modules.

## Repository layout

| Path | Contents |
|---|---|
| `src/igloo_hc.c` | Module entry point and subsystem init order. |
| `src/ehypercall.h`, `src/igloo_hypercall_consts.h` | The per-arch hypercall primitive and its numbers. |
| `src/portal/` | The Portal protocol: dispatch loop, op handlers, shared-memory types. |
| `src/hooks/` | Syscall / ioctl / signal / socket / uname / mount / open hooks. |
| `src/hyperfs/` | Host-backed pseudo-filesystem. |
| `src/netdevs/` | Synthetic network devices (`igloonet`). |
| `scripts/` | Build-time helpers (e.g. trampoline codegen). |
| `docs/` | Sphinx documentation sources. |

## License

The kernel module is GPL-licensed (`MODULE_LICENSE("GPL")`).
2 changes: 2 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_build/
_doxygen/
72 changes: 72 additions & 0 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Doxygen configuration for igloo_driver.
#
# We only want the XML backend: Breathe (in conf.py) consumes the XML and
# Sphinx renders the HTML/PDF. No standalone Doxygen HTML is produced.
#
# This file is driven by conf.py, which sets OUTPUT_DIRECTORY / INPUT via
# environment overrides so a bare `sphinx-build docs/ _build/` is fully
# self-contained (Doxygen runs first, then Sphinx). See docs/conf.py.

PROJECT_NAME = "igloo_driver"
PROJECT_BRIEF = "In-guest kernel agent for IGLOO / Penguin rehosting"

# INPUT and OUTPUT_DIRECTORY are overridden from conf.py via the environment
# (Doxygen expands $(VAR) at parse time). Defaults keep manual runs working
# from the docs/ directory.
INPUT = $(DOXYGEN_INPUT)
OUTPUT_DIRECTORY = $(DOXYGEN_OUTPUT)

RECURSIVE = YES
FILE_PATTERNS = *.c *.h

# `X` is the X-macro helper redefined in several headers (portal_op_list.h
# consumers). Documenting it produces a spurious "Duplicate C declaration"
# across files and carries no reference value — drop it.
EXCLUDE_SYMBOLS = X

# The source is only sparsely commented, so extract every declaration — a
# signature-level reference is still useful, and documented symbols get their
# prose on top. Undocumented members are included too.
EXTRACT_ALL = YES
EXTRACT_STATIC = YES
EXTRACT_PRIVATE = YES
EXTRACT_LOCAL_CLASSES = YES

# XML only; Breathe reads this.
GENERATE_HTML = NO
GENERATE_LATEX = NO
GENERATE_XML = YES
XML_PROGRAMLISTING = YES

# Quiet: warnings about undocumented members would be pure noise here.
QUIET = YES
WARNINGS = NO
WARN_IF_UNDOCUMENTED = NO
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = NO

# Preprocessing. The portal op list is an X-macro; we do NOT try to expand it
# (the generated handle_op_* prototypes are cataloged by hand in portal.md,
# which reads far better than an expanded macro dump). Plain enums, structs and
# functions are extracted literally.
ENABLE_PREPROCESSING = YES
# Expand ONLY the predefined macros below (not the X-macro op list). This lets
# us strip kernel sparse annotations (__user etc.) that the Sphinx C domain
# cannot parse in function-pointer members, while leaving PORTAL_OP_LIST intact.
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
SEARCH_INCLUDES = NO
PREDEFINED = __user= \
__kernel= \
__iomem= \
__force= \
__rcu= \
__must_check= \
__maybe_unused=

# Keep the layout arch-neutral: the per-arch hypercall asm in ehypercall.h is
# guarded by CONFIG_* — define none so every branch's declaration is visible in
# the parse (Doxygen shows the guarded blocks as documented source regardless).

OPTIMIZE_OUTPUT_FOR_C = YES
TYPEDEF_HIDES_STRUCT = YES
Empty file added docs/_static/.gitkeep
Empty file.
22 changes: 22 additions & 0 deletions docs/api/hooks_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Hooks

Syscall, signal, and ioctl hook structures and entry points. See
[Hooks](../hooks.md) for the narrative reference.

## Syscall hooks — `syscalls_hc.h`

```{doxygenfile} syscalls_hc.h
:project: igloo_driver
```

## Signal hooks — `signal_hc.h`

```{doxygenfile} signal_hc.h
:project: igloo_driver
```

## ioctl hooks — `ioctl_hc.h`

```{doxygenfile} ioctl_hc.h
:project: igloo_driver
```
16 changes: 16 additions & 0 deletions docs/api/hypercall_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Hypercall ABI

The per-architecture hypercall primitive and the hypercall number constants.
See [The hypercall ABI](../hypercall_abi.md) for the narrative reference.

## Hypercall numbers — `igloo_hypercall_consts.h`

```{doxygenfile} igloo_hypercall_consts.h
:project: igloo_driver
```

## The hypercall primitive — `ehypercall.h`

```{doxygenfile} ehypercall.h
:project: igloo_driver
```
16 changes: 16 additions & 0 deletions docs/api/hyperfs_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# hyperfs

The host-backed pseudo-filesystem operation set and module entry points. See
[hyperfs](../hyperfs.md) for the narrative reference.

## hyperfs operations — `hyperfs_consts.h`

```{doxygenfile} hyperfs_consts.h
:project: igloo_driver
```

## Module entry points — `hyperfs.h`

```{doxygenfile} hyperfs.h
:project: igloo_driver
```
27 changes: 27 additions & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# C API reference

These pages are **auto-extracted from the source** by Doxygen and rendered with
[Breathe](https://breathe.readthedocs.io/). They reflect the actual
declarations in `src/` at build time — signatures, structs, and enums — with any
in-source documentation comments included.

```{admonition} Coverage
:class: note
The source is only sparsely commented, so these pages are primarily a
*signature-level* reference. The conceptual explanation of each subsystem lives
in the [guide](../index.md) pages; the operation catalog in
[Portal](../portal.md) is the authoritative human-readable list (the handler
prototypes are generated by an X-macro, which is why they are cataloged by hand
there rather than expanded here).
```

```{toctree}
:maxdepth: 1

portal_api
types_api
hypercall_api
hooks_api
hyperfs_api
scope_api
```
Loading
Loading