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
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# LLM Agent Instructions

* To compile `ModRuby`, run these commands:
* `rm -f /usr/src/mod_ruby/CMakeCache.txt`
* `rm -Rf /usr/src/mod_ruby/CMakeFiles`
* `cmake3 /usr/src/mod_ruby`
* `make -j4`
* `make install`
* If any compilation commands fail, the patch failed.
* To test `ModRuby`:
* Reload Apache by running `killall httpd` -- it will automatically restart and load
the new `mod_ruby.so` shared object that was previously compiled.
* Use curl to test `ModRuby` with this: `curl -m1 localhost`
* It should output the string `Ruby check_access()\nHi there from ruby`
* If curl times out and Apache segfaults, the patch failed
* To inspect the backtrace, look at the file `/var/log/httpd/gdb_backtrace.txt`
* After each crash, Apache is restarted
* Each new crash will delete the previous contents of `/var/log/httpd/gdb_backtrace.txt`
* If Apache completely fails to start up due to a symbol linking problem or other error,
the output from the server will be in `/var/log/httpd/error_log` and it will
continue to try and restart itself every 10 seconds. The log file might be large,
so something like `tail -20 /var/log/httpd/error_log` is a safer method to inspect
this file.
* codex is running inside an Oracle Linux 8 Docker container, running as the root user.
If you need tools or packages installed, you may use `dnf install` for distro packages
or use other methods.
* NodeJS 22 is installed and you may install npm packages
* Python 3.12 is installed and you may construct Python utility scripts and execute them.
* If you want to inspect the Ruby source code, it is installed in `/usr/local/rvm/src/ruby-3.2.3/`
* If you want to inspect the Ruby C++ headers, they are in `/usr/local/rvm/src/ruby-3.2.3/include/`
* If you want to inspect the Ruby binary installation, it is installed in `/usr/local/rvm/rubies/ruby-3.2.3/`
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ RUN rvm requirements

# Pick your ruby version here
# Segfaults in rb_protect()
#RUN rvm install ruby-3.2.3
RUN rvm install ruby-3.2.3
# Works
RUN rvm install ruby-2.7.5
#RUN rvm install ruby-2.7.5
# Same segfault as 3.2.3
#RUN rvm install ruby-3.0.3

Expand Down
107 changes: 107 additions & 0 deletions Dockerfile.codex
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# This builds a docker image suitable for running OpenAI's codex CLI
FROM oraclelinux:8

RUN dnf install -y oraclelinux-release-el8 oracle-epel-release-el8 \
&& dnf --enablerepo=ol8_codeready_builder install -y \
apr-devel \
apr-util \
apr-util-devel \
autoconf \
automake \
bison \
bzip2 \
cmake3 \
curl \
cyrus-sasl \
cyrus-sasl-devel \
flex \
gcc \
gcc-c++ \
gdb \
git \
gpg \
httpd \
httpd-devel \
libffi-devel \
libtool \
libyaml \
libyaml-devel \
openssl-devel \
patch \
readline-devel \
ruby \
sqlite-devel \
make \
redhat-lsb \
unzip \
zlib-devel \
&& dnf module enable nodejs:22 \
&& dnf install nodejs nodejs-devel npm \
&& npm install -g @openai/codex

# Import GPG key for RVM
RUN gpg \
--keyserver keyserver.ubuntu.com \
--recv-keys \
409B6B1796C275462A1703113804BB82D39DC0E3 \
7D2BAF1CF37B13E2069D6956105BD0E739499BDB

# Install RVM system wide
RUN curl -sSL https://get.rvm.io | bash -s stable

# Set our shell to a bash full login environment to pull in
# RVM's profile in all RUN instructions below.
# (Requires Docker 1.12)
SHELL ["/bin/bash", "-l", "-c"]

# In case we missed any package requirements, this installs them
RUN rvm requirements

# Pick your ruby version here
# Segfaults in rb_protect()
RUN rvm install ruby-3.2.3 --disable-binary
# Works
#RUN rvm install ruby-2.7.5
# Same segfault as 3.2.3
#RUN rvm install ruby-3.0.3

# Setup our libruby.so dir in ld.so.conf
RUN rvm config-get libdir > /etc/ld.so.conf.d/ruby.conf && ldconfig

WORKDIR /usr/src/mod_ruby

COPY . /usr/src/mod_ruby

# Pulls in the RVM environment and installed ruby
RUN cmake3 . && make -j4 && make install

# Remove some junk that the httpd package installs
RUN rm -f /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.modules.d/00-systemd.conf

# Manually copy some files I couldn't figure out with the CMake system
RUN cp -a config/mod_ruby.conf /etc/httpd/conf.modules.d/

# librhtml.so
RUN cp -a lib/* $(rvm config-get libdir) && ldconfig

COPY docker/index.html /var/www/html/index.html
COPY docker/*.rb /var/www/html/
COPY docker/*.cgi /var/www/cgi-bin/
COPY docker/httpd.conf /etc/httpd/conf/httpd.conf
COPY docker/gdb.input /gdb.input
COPY docker/httpd-gdb /httpd-gdb
COPY docker/httpd-gdb-loop /httpd-gdb-loop

# Force apache logs to docker console logs
#RUN ln -sf /dev/console /var/log/httpd/access_log \
# && ln -sf /dev/console /var/log/httpd/error_log

# Graceful shutdown signal for apache
# (Requires Docker > 1.11)
# Note: gdb is set to trap SIGWINCH, so this is for
# alternate uses with less debugging
#STOPSIGNAL SIGWINCH

# If you want a simpler image without gdb...
#CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
CMD ["/httpd-gdb"]
60 changes: 57 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The build will create these files in the local repo:

To build from source, you need to the following packages:

* Ruby 1.9 or 2.x. (Ruby header files also needed).
* Ruby 2.x or 3.x. (Ruby header files also needed).
* Apache, APR and APR Util headers
* CMake

Expand All @@ -69,15 +69,15 @@ using the Docker image as a handy developer's environment:

bash $ ./script/docker_run
...
0x00007f30bb13733f in accept4 () from /lib64/libc.so.6
0x00007f7a7adfc7b4 in read () from /lib64/libpthread.so.0

A single Apache worker child is running in a gdb shell. Smoke test mod_ruby in a
separate terminal window:

bash $ curl localhost:8080
Hi there from ruby
<html><body>Hello World from HTML!</body></html>

If mod_ruby crashes, gdb will print a full stack trace. You may also do
`Ctrl + C` to break out to a gdb prompt to inspect the running Apache child.

Expand All @@ -87,6 +87,60 @@ cases.

Submit pull requests using feature branches, and have fun!

### Developing with OpenAI Codex

Working with the Ruby C API can be frustrating. Documentation is sparse
and a lot of the experience is trial & error.

This project integrates OpenAI's Codex for an agentic approach. There
is a separate `Dockerfile.codex` which loads in the full Ruby C++ headers
and source code and installs the Codex CLI. The project root contains
an AGENTS.md system prompt to tell the LLM how to compile, install
and test ModRuby. It can run web searches, install packages as root,
search the Ruby source and behave like an entitled twerp.

Apache is started up with the container and will run in a reload loop
if it crashes. The agent is given instructions to test changes and
inspect the stack trace if Apache segfaults. It should keep iterating
until the goal is met, which could be a very long time.

**Use with caution. Don't leave it run unattended.**

First, install [Codex CLI](https://developers.openai.com/codex/cli/)
on your workstation and get the credentials set up by logging into ChatGPT.
The `ModRuby` container bind mounts your workstation's `$HOME/.codex`
into the container and runs the agent inside the container. The `ModRuby`
project root is bind mounted into `/usr/src/mod_ruby` so changes made by the
agent will be reflected immediately on your host OS.

Start up the container with:

bash $ ./script/docker_codex
. . .
To get started, describe a task or try one of these commands:
>

Go wild and have fun.

```
> Search for a potential security vulnerability in ModRuby. Identify the
attack vector and write a test case to confirm the vulnerability. Write
a patch to fix the vulnerability and supply code comments.
```

```
Updated Plan
High-level roadmap before diving into the source and modifications.
Survey key ModRuby modules for input handling to spot potential security issues.
Create a regression test that demonstrates the vulnerability.
Implement a fix with comments, rebuild, and rerun the new test.

I'm focusing on confirming an overflow issue in url_encode by running a
death test with MALLOC_CHECK set to catch heap errors. I'm thinking about
creating a dedicated test compiled with AddressSanitizer to catch the integer
overflow vulnerability in url_encode by triggering a heap-buffer-overflow if present.
```

## License

Redistribution and use in source and binary forms, with or without modification,
Expand Down
88 changes: 88 additions & 0 deletions docker/httpd-gdb-loop
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash
# This is similar to httpd-gdb, except it's more hands-off and
# better suited for automated testing. If apache crashes the
# stack trace will be written to /var/log/httpd/gdb_backtrace.txt
# and then apache is restarted. Each new crash will delete the
# backtrace log and dump the fresh backtrace into it.
#set -euo pipefail

# Config
TRACE_FILE="${TRACE_FILE:-/var/log/httpd/gdb_backtrace.txt}"

mkdir -p "$(dirname "$TRACE_FILE")"

# Clean previous trace each run
rm -f "$TRACE_FILE"

# Ensure child gdb/httpd dies if this script is killed
trap 'pkill -P $$ || true' INT TERM

while :; do
ts="$(date -Is)"
tmp="${TRACE_FILE}.tmp"

echo "[$ts] starting httpd"

rm -f /var/run/httpd/httpd.pid

/usr/sbin/httpd 1>>"$tmp" 2>>"$tmp"

ret=$?
if [[ "$ret" != "0" ]]; then
{
echo "Failed to start up Apache:"
echo
} >>"$tmp"
mv -f "$tmp" "$TRACE_FILE"
# longer sleep time on big time fails
sleep 10
continue
fi

sleep 2
pid=$(ps ax |grep /usr/sbin/httpd |grep -v grep | grep -v defunct | awk '{print $1}' |tail -1)
echo "httpd fork pid: $pid"

# If apache started up successfully and the previous trace file
# contains a fail message, delete the trace file. We want to keep
# backtraces intact for observability though.
if grep -q "Failed to start up Apache" "$TRACE_FILE"; then
echo "Deleting trace file"
rm -f "$TRACE_FILE"
fi

#-ex "set detach-on-fork on" \
#-ex "handle SIGPIPE nostop noprint pass" \
#-ex "handle SIGSEGV stop print nopass" \
#-ex "set follow-fork-mode child" \
gdb -p $pid -q --batch \
-ex "set pagination off" \
-ex "set confirm off" \
-ex "handle SIGPIPE nostop noprint pass" \
-ex "handle SIGSEGV stop print nopass" \
-ex "continue" \
-ex "echo \n===== THREAD BACKTRACE =====\n" \
-ex "thread apply all bt full" \
-ex "echo \n===== REGISTERS =====\n" \
-ex "info registers" \
2>&1 | tee "$tmp"

# If it crashed, keep the trace; else discard.
if grep -q "received signal SIGSEGV" "$tmp"; then
{
echo "===== TIMESTAMP ====="
echo "$ts"
} >>"$tmp"
mv -f "$tmp" "$TRACE_FILE"
echo "segfault detected. backtrace saved to: $TRACE_FILE"
else
cat "$tmp"
rm -f "$tmp"
echo "no segfault detected. restarting."
fi

ps ax |grep /usr/sbin/httpd |grep -v grep | grep -v defunct | awk '{print $1}' | xargs kill -9

sleep 2
done

9 changes: 9 additions & 0 deletions docker/httpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ DocumentRoot "/var/www/html"
# Allow open access:
Require all granted
</Directory>
# Make apache chill out on the event MPM settings so that
# we don't have such a huge mess of threads in gdb to deal with
MaxClients 1
ThreadsPerChild 1
MaxRequestWorkers 1
MinSpareThreads 1
MaxSpareThreads 1
MaxConnectionsPerChild 0
ServerLimit 1

CoreDumpDirectory /tmp
RubyHandlerDeclare TEST
RubyHandlerModule TEST "/var/www/html/test.rb"
Expand Down
26 changes: 26 additions & 0 deletions script/docker_codex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
root=$( readlink -f $( dirname $( readlink -f $0 ) )/.. )
docker build -f $root/Dockerfile.codex -t mod_ruby_codex $root

docker rm -f mod_ruby_codex_run_container
# Change 8080 here if you have a local conflict with it
docker run \
--name=mod_ruby_codex_run_container \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
-v $root:/usr/src/mod_ruby \
-v $HOME/.codex:/root/.codex \
-d \
-p 8080:80 \
mod_ruby_codex \
/bin/sleep infinity

# Start up Apache in the background. This script will loop
# restarting Apache when it crashes. Stack traces are logged
# for the Codex agent to inspect.
docker exec -d mod_ruby_codex_run_container /httpd-gdb-loop
#docker exec -ti mod_ruby_codex_run_container /httpd-gdb-loop

# Run the Codex agent with full access and web search
docker exec -ti mod_ruby_codex_run_container codex --sandbox danger-full-access --search --cd /usr/src/mod_ruby

Loading