Skip to content

Commit

Permalink
kernelCTF: PR GHA: support KASLR leak for repro
Browse files Browse the repository at this point in the history
  • Loading branch information
koczkatamas committed Sep 17, 2023
1 parent 7e10b04 commit 4a6532e
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 26 deletions.
35 changes: 21 additions & 14 deletions .github/workflows/kernelctf-submission-verification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ jobs:
env:
RELEASE_ID: ${{ matrix.target }}
SUBMISSION_DIR: ${{ needs.structure_check.outputs.submission_dir }}
EXPLOIT_INFO: ${{ needs.structure_check.outputs[format('exploit_info_{0}', matrix.target)] }}
defaults:
run:
shell: bash
working-directory: ./kernelctf/repro/
steps:
- name: Checkout repo content
uses: actions/checkout@v3
Expand All @@ -133,69 +138,71 @@ jobs:
uses: actions/download-artifact@v3
with:
name: exploit_${{ env.RELEASE_ID }}
path: exp/
path: ./kernelctf/repro/exp/

- name: Fetch rootfs
run: |
wget https://storage.googleapis.com/kernelctf-build/files/rootfs_repro_v1.img.gz
mv rootfs_repro_v1.img.gz rootfs.img.gz
wget -O rootfs.img.gz https://storage.googleapis.com/kernelctf-build/files/rootfs_repro_v2.img.gz
gzip -d rootfs.img.gz
- name: Download bzImage
run: |
if [ "$RELEASE_ID" == "mitigation-6.1" ]; then RELEASE_ID="mitigation-6.1-v2"; fi
wget https://storage.googleapis.com/kernelctf-build/releases/$RELEASE_ID/bzImage
- name: List repro folder contents
run: ls -alR ./

# ugly hack to make Github Actions UI to show repro logs separately in somewhat readable fashion
- id: repro1
name: Reproduction (1 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 1
run: ./repro.sh 1

- id: repro2
name: Reproduction (2 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 2
run: ./repro.sh 2

- id: repro3
name: Reproduction (3 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 3
run: ./repro.sh 3

- id: repro4
name: Reproduction (4 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 4
run: ./repro.sh 4

- id: repro5
name: Reproduction (5 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 5
run: ./repro.sh 5

- id: repro6
name: Reproduction (6 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 6
run: ./repro.sh 6

- id: repro7
name: Reproduction (7 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 7
run: ./repro.sh 7

- id: repro8
name: Reproduction (8 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 8
run: ./repro.sh 8

- id: repro9
name: Reproduction (9 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 9
run: ./repro.sh 9

- id: repro10
name: Reproduction (10 / 10)
continue-on-error: true
run: ./kernelctf/repro.sh 10
run: ./repro.sh 10

- name: Upload repro QEMU logs as an artifact
uses: actions/upload-artifact@v3
Expand All @@ -208,4 +215,4 @@ jobs:
STEPS: ${{ toJSON(steps) }}
run: |
echo $STEPS >> steps.json
./kernelctf/repro_summary.py ${{ github.run_id }}
../repro_summary.py ${{ github.run_id }}
1 change: 1 addition & 0 deletions kernelctf/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.cache
39 changes: 29 additions & 10 deletions kernelctf/check-submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
import csv
import io
import hashlib
import time

BASE_DIR = os.path.abspath(os.path.dirname(__file__))
PUBLIC_CSV_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vS1REdTA29OJftst8xN5B5x8iIUcxuK6bXdzF8G1UXCmRtoNsoQ9MbebdRdFnj6qZ0Yd7LwQfvYC2oF/pub?output=csv"
POC_FOLDER = "pocs/linux/kernelctf/"
EXPLOIT_DIR = "exploit/"
CACHE_DIR = f"{BASE_DIR}/.cache"
MIN_SCHEMA_VERSION = 2
DEBUG = "--debug" in sys.argv
# DEBUG = "--debug" in sys.argv

errors = []
warnings = []
Expand Down Expand Up @@ -80,10 +83,19 @@ def checkRegex(text, pattern, errorMsg):
error(f"{errorMsg}. Must match regex `{pattern}`")
return m

def fetch(url):
def fetch(url, cache_name, cache_time=3600):
cache_fn = f"{CACHE_DIR}/{cache_name}"
if cache_name and os.path.isfile(cache_fn) and (time.time() - os.path.getmtime(cache_fn) < cache_time):
with open(cache_fn, "rb") as f: return f.read().decode('utf-8')

response = requests.get(url)
if response.status_code != 200:
fail(f"expected 200 OK for request: {url}")

if cache_name:
os.makedirs(CACHE_DIR, exist_ok=True)
with open(cache_fn, "wb") as f: f.write(response.content)

return response.content.decode('utf-8')

def parseCsv(csvContent):
Expand All @@ -93,7 +105,7 @@ def parseCsv(csvContent):
argv = [arg for arg in sys.argv if not arg.startswith("--")]
print(f"[-] Argv: {argv}")

mergeInto = argv[1] if len(argv) >= 2 else "origin/main"
mergeInto = argv[1] if len(argv) >= 2 else "origin/master"
print(f"[-] Params: mergeInto = {mergeInto}")

mergeBase = run(f"git merge-base HEAD {mergeInto}")[0]
Expand Down Expand Up @@ -149,7 +161,7 @@ def parseCsv(csvContent):
schemaVersion = MIN_SCHEMA_VERSION

schemaUrl = f"https://google.github.io/security-research/kernelctf/metadata.schema.v{schemaVersion}.json"
schema = json.loads(fetch(schemaUrl))
schema = json.loads(fetch(schemaUrl, f"metadata.schema.v{schemaVersion}.json"))

metadataErrors = list(jsonschema.Draft202012Validator(schema).iter_errors(metadata))
if len(metadataErrors) > 0:
Expand All @@ -161,11 +173,7 @@ def parseCsv(csvContent):
submissionIds = [submissionIds]
print(f"[-] Submission IDs = {submissionIds}")

if DEBUG:
with open("public.csv", "rt") as f: publicCsv = f.read()
else:
publicCsv = fetch(PUBLIC_CSV_URL)

publicCsv = fetch(PUBLIC_CSV_URL, "public.csv")
publicSheet = { x["ID"]: x for x in parseCsv(publicCsv) }
# print(json.dumps(publicSheet, indent=4))

Expand Down Expand Up @@ -207,6 +215,8 @@ def parseCsv(csvContent):
print(f"[-] Got flags for the following targets: {', '.join(flagTargets)}")
checkList(flagTargets, lambda t: t in exploitFolders, f"Missing exploit for target(s)")
checkList(exploitFolders, lambda t: t in flagTargets, f"Found extra exploit(s) without flag submission", True)
if schemaVersion >= 3:
checkList(flagTargets, lambda t: t in metadata["exploits"].keys(), f"Missing metadata information for exploit(s)")

def ghSet(varName, content):
varName = f"GITHUB_{varName}"
Expand All @@ -224,8 +234,17 @@ def summary(success, text):
if len(errors) > 0:
summary(False, f"The file structure verification of the PR failed with the following errors:\n{formatList([f'❌ {e}' for e in errors], True)}")

ghSet("OUTPUT", "targets=" + json.dumps([f for f in exploitFolders if not f.startswith("extra-")]))
ghSet("OUTPUT", "targets=" + json.dumps([f for f in flagTargets]))
ghSet("OUTPUT", f"submission_dir={subDirName}")

for target in flagTargets:
if schemaVersion >= 3:
exploit_info = metadata["exploits"].get(target)
if not exploit_info: continue
exploit_info = { key: exploit_info[key] for key in ["uses", "requires_separate_kaslr_leak"] if key in exploit_info }
else:
exploit_info = {}
ghSet("OUTPUT", f"exploit_info_{target}={json.dumps(exploit_info)}")

summary(True, f"✅ The file structure verification of the PR was successful!")

23 changes: 23 additions & 0 deletions kernelctf/repro/init/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash
set -ex
mount -t proc none /proc
mount -t sysfs none /sys

mkdir /tmp/exp_ro
mount -t 9p exp /tmp/exp_ro

mkdir /tmp/exp
chown user:user /tmp/exp
chmod a+rx /tmp/exp

cp /tmp/exp_ro/* tmp/exp/
chmod a+rx /tmp/exp/*

CMD="/tmp/exp/exploit"
if [[ " $* " == *" kaslr_leak=1 "* ]]; then
KASLR_BASE=`head -n 1 /proc/kallsyms | cut -d " " -f1`
CMD="$CMD $KASLR_BASE"
fi

echo "running exploit, cmd=$CMD"
su user -c "$CMD"
8 changes: 6 additions & 2 deletions kernelctf/repro.sh → kernelctf/repro/repro.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ touch $QEMU_TXT

START_TIME=$(date +%s)

CMDLINE="console=ttyS0 root=/dev/vda1 rootfstype=ext4 rootflags=discard ro init=/init hostname=repro"
if echo $EXPLOIT_INFO | jq -e '.requires_separate_kaslr_leak'; then CMDLINE="$CMDLINE -- kaslr_leak=1"; fi

expect -c '
set timeout -1
set stty_init raw
Expand All @@ -27,8 +30,9 @@ expect -c '
-nic user,model=virtio-net-pci \
-drive file=rootfs.img,if=virtio,cache=none,aio=native,format=raw,discard=on,readonly=on \
-drive file=flag,if=virtio,format=raw,readonly=on \
-virtfs local,path=exp,mount_tag=exp,security_model=none \
-append "console=ttyS0 root=/dev/vda1 rootfstype=ext4 rootflags=discard ro init=/init hostname=repro" \
-virtfs local,path=init,mount_tag=init,security_model=none,readonly=on \
-virtfs local,path=exp,mount_tag=exp,security_model=none,readonly=on \
-append "'"$CMDLINE"'" \
-nographic -no-reboot
expect "# "
Expand Down

0 comments on commit 4a6532e

Please sign in to comment.