Use-after-free depends on the codebase you’re working on to determine whether it’s exploitable… but given an example code base, it’s insidiously easy to create…
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func()
{
printf("Hello world\n");
}
void func2()
{
printf("How did I get here?\n");
}
struct s_ops {
void (* f) ();
};
int main()
{
struct s_ops *a = malloc(sizeof(struct s_ops));
a->f = func;
a->f();
free(a); // use-after-free... free here, and use 'a' again later on
struct s_ops *b = malloc(sizeof(struct s_ops));
b->f = func2;
a->f();
}
The corewars simulator I used wasn’t the MARS redcode simulator… more info here…
AI Mode conversation
You said: redcode corewarsredcode corewars
Redcode is an assembly-like programming language used to write battle programs called “warriors” for the classic computer programming and hacking game Core War.
Core Concepts
The Environment: Warriors run inside a virtual computer called MARS (Memory Array Redcode Simulator), which features a circular array of memory cells (typically 8,000 cells).
The Goal: Programs execute alternately, attempting to disrupt, overwrite, or cause the opponent’s code to execute an illegal instruction (such as DAT) to terminate all of its processes.
Redcode Syntax: Each instruction consists of an opcode, two operands (A-field and B-field), addressing modes, and optional modifiers (e.g., MOV 0, 1 or DAT.F $0, $0).
Common Redcode Instructions
DAT (Data): Stores data; executing this instruction kills the current process.
MOV (Move): Copies data or instructions from one memory address to another.
ADD / SUB (Add / Subtract): Performs basic arithmetic on memory fields.
JMP (Jump): Unconditional branch to another instruction address.
SPL (Split): Creates a new parallel process, enabling multitasking/multithreading.
IMP (Imp): A classic, minimalist mobile warrior that continuously copies itself to the next memory address (MOV 0, 1 then JMP -1).
Common Warrior Strategies
Bomber (Stone): Blindly and repeatedly writes “bomb” instructions (like DAT) across the core memory to hit the enemy.
Scanners (Scissors): Searches the memory core to locate the opponent’s code before attacking the detected location.
Replicators (Paper): Constantly creates copies of itself across the core to out-survive and overwhelm enemy attacks. Would you like to see a sample Redcode warrior script (such as the classic Imp or Dwarf), or learn how to test code in a web-based MARS simulator?
Corewars Beginner’s guide to Redcode Core War: Programming game where abstract assembly programs (warriors) execute alternately in a circular memory core (array of ins…
* Wikipedia Core War – Wikipedia Overview: 1984 programming game by D.G. Jones and A.K. Dewdney where battle programs (warriors) compete in a virtual computer (cor…
* Read the Docs Intro – Corewar.io Documentation Language: Redcode assembly-like language used to write Corewar warriors, consisting of an opcode and two operands (e.g., mov 0, 1) Show all
corewars from redhat 6 or redhat 7 wasn’t a redcode one… do you know of it?
Yes, you are likely thinking of a popular open-source software package simply called corewars (usually version 0.9.x, such as corewars-0.9.13), which was widely bundled in the “Games” or “Strategy” repositories of Linux distributions like Red Hat Linux 6.x and 7.x in the late 1990s and early 2000s. While it was a simulation game where programs tried to crash each other, it differed significantly from standard, hardcore ICWS-compliant Redcode systems (like pMARS) in a few key ways:
It Was Built Around an Abstract x86-like Assembly, Not Pure Redcode
While inspired by Core War, this specific corewars Linux package was intended as a graphical tool to help computer science students and hobbyists learn real-world Assembly programming (primarily mimicking basic Intel x86/register-based logic).
Instead of the abstract relative-addressing fields of Redcode (A-fields and B-fields with symbols like $, @, <), it used registers (like ax, bx) and standardized instructions.
It felt much more like coding a mini x86 assembly program than writing a standard Redcode “warrior”.
The Iconic GTK+ Graphical Interface
If you played this on Red Hat 6 or 7, you likely remember its GTK+ graphical user interface. It didn’t look like a command-line simulator or a text-based terminal grid.
It featured a prominent live memory grid window where you could see the bytes of different programs color-coded (e.g., player one’s code in red, player two’s in blue) slowly overwriting or jumping through each other in real-time.
It featured a side-by-side statistics window tracking active processes, program lengths, and CPU execution cycles.
A Closed, Fixed “Toy” Environment
Unlike the infinite variations of the modern Redcode standards, this specific Linux port came pre-packaged with just a handful of classic sample assembly bots (default.asm, etc.) designed to show you how basic assembly loops, pointer increments, and register shifts worked. It was incredibly popular on early Linux desktop installations because it was one of the few animated, technical strategy games you could install natively via a standard package or lightweight tarball. Does the memory of a color-coded grid using an x86/register syntax ring a bell? If you are trying to track down that specific old software or want to find a way to run those .asm style programs today, let me know!
* Corewars Beginner’s guide to Redcode The Redcode instruction set The Imp The truth is, the most important parts of Redcode are the easiest ones. Most of the basic warr…
* Wikipedia Core War – Wikipedia Overview: 1984 programming game by D.G. Jones and A.K. Dewdney where battle programs (warriors) compete in a virtual computer (cor…
Beej.us Core War Frequently Asked Questions (rec.games.corewar FAQ) Redcode has been standardized by the ICWS, and is therefore transportable between all standard Core War systems. Show all
where is it now? i can’t find the source code for it anywhere… is it dead?
The game is technically “dead” in terms of modern updates—the last stable release was version 0.9.13, published back on October 31, 2002. However, because it was released under a free software GPLv2 license, the source code is not lost. It is just buried deep inside ancient open-source archives. The specific project you are remembering was developed by Philip Thorne and Walter Hofmann. It featured a custom, simplified assembly-like language as its default mode alongside a graphical GTK+ interface.
Where to Find the Source Code
The code is still hosted on its original, legacy project pages. You can download the pristine tarballs here:
SourceForge Repository: The project remains frozen exactly as it was left on the Corewars SourceForge Files Page.
Direct Source Link: You can pull the exact last release directly via the corewars-0.9.13.tar.gz download link.
Why is it so hard to run today?
If you download that source code, you will likely struggle to compile it on a modern Linux distribution (like Ubuntu 24.04 or Fedora 40) out of the box. The game relies entirely on GTK+ 1.2, an incredibly old version of the GIMP Toolkit that was replaced by GTK2 in 2002 and GTK3/4 since. Modern C compilers will also reject the vintage 1990s C syntax without passing specific legacy flags (like -std=gnu89) to the compiler.
How to Play it Now
If you want to experience that exact x86/register style game loop or run the source code, you have two realistic paths:
Run a Retro Linux VM: Spin up a lightweight virtual machine running an era-appropriate operating system like Red Hat Linux 7.3 or Debian 3.0 (Woody). The game will install natively or compile instantly.
Look at the OpenBSD Package: Interestingly, the OpenBSD community has actively maintained a port of it in their package trees (listed as corewars-0.9.13p12), patching the ancient source code over the years to keep it alive on their platform. Would you like some help trying to compile the old code on a modern system, or would you prefer recommendations for modern alternatives that use real x86 Assembly for programming battles?
Send me the Core War code (Redcode), and I’ll review it.
I’ll look at strategy, efficiency, survivability, and how well it performs against other warriors, including:
Attack strategy: How effectively it disables opponents.
Defense: How vulnerable it is to scanners, bombers, and replicators.
Optimization: Whether instructions, addressing modes, or timing can be improved.
Competitive strength: How it might perform against common Core War strategies.
I’ll also suggest improvements while keeping the original strategy intact.
Paste the code here, and let me know whether you’re using ICWS ’88 or ICWS ’94, if you know.
Pasted markdown.mdDocument
Code review: AAA – Neuron v4
My impression: this is a remarkably ambitious Core War warrior. What interests me most isn’t its bombing capability, but the fact that you’ve effectively built a small, distributed, message-passing system inside a Core War environment.
Rather than implementing a conventional replicator, scanner, or bomber, you’ve created a network of autonomous neurons that can reproduce, relocate, communicate, and change their behavior in response to commands.
That’s a genuinely interesting architecture.
What I particularly like
1. The signaling protocol
Your signalstatus mechanism uses three states:
0 — ready to receive
1 — signal being written
2 — signal ready for processing
Combined with the 128/256 directional propagation flags and 512 execution flag, this allows you to broadcast commands through a linked network of neurons.
It’s a clever way of separating coordination from execution.
The same cloning machinery supports both reproduction and relocation by changing its continuation address.
That’s an elegant way to reuse instructions when you’re constrained to 200.
3. Self-preserving replication
Your use of own [r] to avoid overwriting your own process’s memory is particularly interesting.
Combined with the linked-list architecture, this suggests you’re trying to build a distributed organism that can expand without accidentally destroying itself.
4. Runtime behavioral changes
The fact that neurons can become bombers, relocate, replicate, or transform into imps means you’ve effectively separated the warrior’s structure from its strategy.
The bootstrap currently cycles between signaling all neurons to perform action 4 (safe bombing) and action 2 (relocation).
That gives the system a degree of adaptability unusual for a simple Core War warrior.
Where I see potential problems
1. The race condition is more serious than it first appears
You’ve already identified the race between sendsignal() and moveneuron().
The underlying problem is that your communication protocol assumes a neuron’s address remains valid during message delivery.
But relocation violates that assumption.
For example:
Neuron A begins sending a signal to neuron B.
Neuron B relocates and updates its neighbors.
Neuron A continues polling B’s old signalstatus.
The signal may never reach B, and A can become stuck.
There is also a related concern: checking the marker 1337 before sending doesn’t guarantee the target will remain valid throughout the operation.
This is a classic distributed-systems problem involving concurrent modification and stale references.
Two senders could both observe zero before either writes one.
Whether this actually occurs depends on the Core War virtual machine’s scheduling and process model, but the code itself doesn’t guarantee mutual exclusion.
Your protocol is therefore closer to a cooperative handshake than a true lock.
3. Replication checks only the boundaries
In clone():
clone: info 2, r own [r] jump clone
You check the beginning and end of the destination region, but not every cell between them.
If own only tests an individual address, another neuron could occupy the middle of that region.
That creates a possible self-corruption scenario even when both boundary checks succeed.
4. Your network is vulnerable to broken links
Each neuron maintains prevneuron and nextneuron.
That’s efficient, but it also creates dependencies.
If a neuron is destroyed, its neighbors may retain pointers to invalid locations. A subsequent broadcast could terminate or stall when it encounters the damaged link.
Your sendsignal() validation catches some invalid targets, but responds with:
data 0
That protects against continuing with a bad address, but it doesn’t recover the network.
The most interesting strategic question
Your architecture seems optimized for coordinated behavior rather than raw attack speed.
That’s a meaningful tradeoff.
A conventional bomber spends most of its instructions attacking. Your neurons spend considerable effort maintaining communication, checking ownership, and coordinating movement.
The payoff is that you can command a distributed network to switch behaviors.
But whether this wins battles depends on whether that flexibility compensates for its overhead.
I’d particularly want to measure the percentage of cycles spent on communication versus bombing.
My assessment
Category
Assessment
Originality
Exceptional
Architecture
Very sophisticated
Code reuse
Excellent
Concurrency safety
Fragile
Fault tolerance
Limited
Attack efficiency
Uncertain
Potential for experimentation
Very high
These are architectural judgments from reading the source, not results from running matches.
One thing I’d investigate immediately
I think there may be an opportunity to improve the relocation protocol without adding an entirely separate communications system.
Your signalstatus already has three states, and your relocation operation already updates the neighboring pointers.
It might be possible to reuse some of those mechanisms to establish a temporary relocation state or forwarding behavior, although fitting that within 200 instructions would be difficult.
The important challenge is making the relocation and signaling operations agree on which address is authoritative.
One question: which Core War implementation is this running on?
This isn’t standard ICWS ’88 or ’94 Redcode. Instructions such as info, own, movei, and fork, along with your use of absolute addressing, suggest a different virtual machine.
I’d be interested to know which one, because its scheduling and memory semantics will determine whether several of the concurrency issues I’ve identified are genuine bugs or merely theoretical risks.
Overall, I find the distributed-neuron design considerably more interesting than a conventional Core War replicator.
check this corewars code out, it’s a statistically “optimal” one, based off standard deviations, etc, based off the number of files loaded, their code sizes, core size, etc
r: data 0
coresize / nprogs = 273
avg prog size: 35.65
std dev: 31.49
thus, desired search points:
[ |1dev| (midpoint) |1dev| ]
make next midpoint here: ---------------------> ^^^^^^
midpoint = interval / 2 = 136
main: move 0, [r] # midpoint
add -31, r
move 0, [r] # left 1dev
add 62, r
move 0, [r] # right 1dev
add 74, r
jump main
…
This is actually a really interesting approach to Core War. You’re treating the core as a statistical target-distribution problem, rather than trying to find enemy programs through conventional scanning or random bombing.
And there’s something particularly clever about your choice of three bombing positions: you’re attempting to maximize the probability of hitting an enemy program using information about the distribution of program sizes.
I do see one important mathematical distinction between statistically informed and statistically optimal, though.
1. What your algorithm is doing
You’ve established:
Parameter
Value
Average core space per program
273 cells
Mean program size
35.65 instructions
Standard deviation of program size
31.49 instructions
Search interval midpoint
136 cells
Bombing positions per iteration
3
Offset between adjacent bombs
~31 cells
Your bombing pattern is effectively:
ONE 273-CELL SEARCH INTERVALThree bombs centered around the midpoint, separated by approximately one standard deviation of program length.
You’re then advancing through the core and repeating the pattern.
The advantage is that you’re not wasting three bombs on consecutive cells that might all belong to the same enemy program.
Instead, you’re spreading them apart to increase the chance of hitting different targets.
That is a sensible optimization objective.
2. Why the standard deviation matters — and where it doesn’t
Your use of the standard deviation is interesting because it describes the variability in enemy program lengths.
However, the standard deviation of program length is not the standard deviation of program location.
Those are different random variables.
For example, if programs are uniformly distributed around the core, there is no particular reason an enemy is more likely to occupy the midpoint of a 273-cell interval than another position.
The mean length of 35.65 and standard deviation of 31.49 tell us about how large programs are, not where they begin.
Nevertheless, these statistics are useful for choosing the spacing between bombs.
The optimization you really want
Suppose an enemy occupies a contiguous block of L instructions.
If you place three bombs at positions separated by d, the probability of hitting that program depends on how much the three possible hit regions overlap.
When bombs are too close together, their coverage overlaps heavily.
When they’re farther apart, they can cover more distinct possible starting positions.
With a uniformly random starting location on a sufficiently large circular core, and three equally spaced bombs with gaps d, the effective coverage is:
C(L,d)=L+2min(L,d)
provided the remaining wraparound gap is at least L.
That gives a useful insight:
Spacing bombs approximately one program length apart can reduce redundant hits.
But the ideal spacing isn’t necessarily one standard deviation. It depends on the full distribution of lengths.
3. There’s a subtle issue with the actual code
Here’s your loop:
main: move 0, [r] add -31, r move 0, [r] add 62, r move 0, [r] add 74, r jump main
Let’s trace the addresses, starting with r = 0.
Iteration
First bomb
Second bomb
Third bomb
1
0
-31
31
2
105
74
136
3
210
179
241
4
315
284
346
Your net movement per iteration is:
−31+62+74=105
So you’re advancing 105 cells per iteration, rather than 273.
This means your pattern is not actually visiting the midpoint and ±31 of successive 273-cell intervals.
Instead, it creates a repeating pattern with gaps of:
SORTED BOMB LOCATIONSAfter sorting the addresses, the gaps follow a 31, 43, 31 pattern, rather than independent 273-cell intervals.
And this might actually be a better pattern than your stated interval approach!
You’ve accidentally—or perhaps deliberately—created a relatively dense, nonuniform bombing sequence with alternating gaps.
That could be effective against programs of around 35 instructions, because the spacing is close to their average length.
4. What I’d optimize next
I’d compare your current pattern against other spacings using the actual distribution of program sizes.
For a simple model with uniformly random program starting positions, we can calculate the expected probability of hitting at least one cell of an enemy program:
P(hit)=L∑P(L)NC(L)
where P(L) is the probability that an enemy program has length L, C(L) is the number of distinct starting positions covered by the bombs, and N is the core size.
This would allow us to test whether spacing by 31, 35, 40, or some other value produces the highest expected hit rate.
One caveat: your mean and standard deviation alone don’t determine the full program-length distribution. We’d need either the actual program sizes or an assumed distribution.
And the best bombing sequence also depends on how quickly the enemy acts, whether processes move or clone, and how much damage a single zero-write actually causes.
My verdict
I like the underlying idea a lot. You’re applying statistical information about the population of enemy programs to a tactical problem that is often approached with fixed heuristics.
The strongest part of the design is that you’re trying to minimize redundant bombing by matching bomb spacing to the characteristic size of enemy programs.
The weakest part of the mathematical argument is treating one standard deviation as inherently optimal. It may be a good heuristic, but it isn’t established as the optimum by the numbers you’ve supplied.
One thing I’m particularly curious about: was the add 74, r intentional?
Because that produces a 105-cell stride and the 31/43/31 gap pattern. If that was deliberate, I’d be interested in your reasoning—it makes the code more interesting than simply bombing three positions every 273 cells.
This is a neuron-like corewars set of code I wrote back in 2006…
From the readme.txt:
This is a collection of "neuron-like" corewars code.
Whilst the signals being passed (at least up to v4) are basically pre-programmed actions,
one could alter this and instead use the neurons to process some input,
like a neural network.
Although there is no real advantage in corewars to do so.
Perhaps there is a better way to create "artificial intelligence" in corewars.
--
Another way, is using an "evolver" (see simple-evolver) where it takes code from
a random location and tries to store and execute it. If it fails, the code is discarded.
--
Another way, is the typical parasite method, which is by forking into random code. But
this can be easily defeated by the forkbomb.cw code.
--
The only other way i can think of is, some piece of code like this, with lots of jumps...
addr: data 0 # this gets randomised (within the boundaries of the code)
r: data 0 # just a var
line1: some instruction here, e.g. move 0, [r]
some instruction here to alter addr, e.g. add 1, addr
jump [addr]
line2: some instruction here
some instruction here to alter addr
jump [addr]
line3: some instruction here
...
...
...
and so on.
It then boils down to the "altering addr" method, which can be stored into a table or
based on "learning" or "statistics" -- thus improving its code over time, without
actually changing anything (except the order it's run).
E.g. you could use an sbox to hold all the addrs of code. Each "jump instruction"
jumps into a function which then permutates the sbox and returns a new addr accordingly.
The permutation function can then be optimised for a particular scenario.
This is similar to "neuron" with the signal handling/actions.
--
Here’s version 4 of the code:
# NB: This is complete and working, but there is a race condition with "sendsignal()"
# and "clone()" which can prevent signals being sent to newly moved neurons (moveneuron)
# because sendsignal() may be polling on the old location and doesn't know that it has moved.
#
# The only way to fix this is to implement a communications protocol similar to
# process_protect and process_module.
#
# But this code has already reached the limit of 200 instructions, so there is no way
# to fix this problem.
#
#
title "AAA - Neuron v4"
author "---------- DS -----------"
bootthreads: data 1 # initial case: minimum 1 more threads
# after "first neuron" and its clone...
# i.e. total = 3 threads
# self-signal to create initial neuron
boot: move 2, signalstatus
move 1, signal
add 256, signal
# move &bootstrap, jumpback
move &bootstrap2, jumpback
jump checksignal
#bootstrap: move 1, sendsignaldata # issue "create neuron" command to last neuron
# add 256, sendsignaldata
# move nextneuron, sendsignalneuron # &neuronmarker can be used instead of nextneuron
# move &bootstrap1, jumpback # in non-bootstrap "first neuron" type code.. i.e. with a poll loop
# jump sendsignal # otherwise, in bootstrap code, you need to checksignal()
# to get it to work.
# Since we have a cloned neuron already, due to this bootstrap,
# we just use it instead.
#bootstrap1: loop bootthreads, bootstrap
bootstrap2: move 4, sendsignaldata # issue new command action, for all neurons
add 256, sendsignaldata
add 512, sendsignaldata
move nextneuron, sendsignalneuron
move &bootstrap3, jumpback
jump sendsignal
bootstrap3: move &bootstrap5, jumpback
jump delay
bootstrap5: move 2, sendsignaldata # move neurons
add 256, sendsignaldata
add 512, sendsignaldata
move nextneuron, sendsignalneuron
move &bootstrap6, jumpback
jump sendsignal
bootstrap6: move &bootstrap2, jumpback
jump delay
delay: move 1000, i
delay2: loop i, delay2
jump [jumpback]
# ----------- begin neuron here --------------------------------------
# signals (sent as bit flags):
#
# (action signals) - you'd want one of these (you can combine these):
# 0 = do nothing.
# 1 = create more neurons
#
# (action signals with special conditions) - or these (don't combine these):
# 2 = move this neuron into a new cell
# since this moves the current neuron into a new place, you can't combine this.
# 4 = move 0, [random] (safe version, does not kill other neurons of same process)
# aborts on receiving another signal
# 8 = move 0, [random] (unsafe version)
# aborts on receiving another signal
# 32 = convert into an imp
# neuron transforms into an imp
# 64 = terminates
# neuron terminates
#
# (signal handling signals) - and you'd want one of these (at least one of these):
# 128 = signal prev neuron with this same signal
# 256 = signal next neuron with this same signal
# 512 = do action (even if you're not the last neuron to get this signal)
#
# NB: When you combine the flags, they will be handled in numerical order.
# Special conditions may prevent further flags from being "actioned".
# Also, signal propagation occurs FIRST before actions, so you cannot
# expect "newly created" neurons to receive a signal until the next signal is sent.
# (e.g. This is the only way to prevent signal 1 creating an infinite number of
# neurons. Similar problems occur with signal 2.)
# ------------- global variables: the values will not be copied on clone()->linkneuron() -----------------------------------
neuronmarker: data 1337
# --- public vars ---
signalstatus: data 0
signal: data 0
nextneuron: data 0
prevneuron: data 0
# --- private vars ---
jumpback: data 0 # jumpback address for various code
jumpbackclone: data 0 # jumpback address for clone code (only)
recvsignal: data 0 # received signal's value
sendsignaldata: data 0 # signal value to be sent
sendsignalneuron: data 0 # target address of neuron for sendsignal
s: data 0 # generic vars -- may be clobbered when entering a new function call.
r: data 0
i: data 0
# --- constants ---
sizeofvars: data 12 # size of all variables
entrypointoffset: data 15 # entry point offset from &neuronmarker
# ------------ main polling loop --------------------------------
# Clobbered: jumpback
main: jump main2
# data 0 # if your entrypointoffset is incorrect, it will terminate
# data 0 # as soon as it forks into the wrong offset.
# data 0 # (this is only useful for debugging)
# data 0 # but this exceeds the "max limit" of 200 instructions.
# data 0 # Uncomment as many as you want.
main2: move 0, signalstatus # clear signalstatus
poll: move &poll, jumpback
poll1: equal 2, signalstatus # poll signalstatus
jump checksignal
jump poll1
# ------------- Function: checksignal() ---------------------------------
# Clobbered but restored: jumpback
# Clobbered: signalstatus
checksignalret: data 0 # caller's return address
checksignal: move jumpback, checksignalret # save caller's return address
move signal, recvsignal # save it into recv signal, and reset signalstatus
checksignal0: equal 0, recvsignal # do nothing?
jump checksignalend
# similar to doaction()'s switch-case, but optimised for speed (not code size).
# i.e. does not do multiple evaluations but requires more opcodes for control.
checksignal128: move 128, i # check 'signal prev' flag
and recvsignal, i
equal 128, i
jump sendsignal128
jump checksignal256 # ->--+
# |
# |
sendsignal128: equal prevneuron, 0 # <= if we're the last neuron in this signal chain
jump checksignaldo # <= do the action.
move recvsignal, sendsignaldata # |
move prevneuron, sendsignalneuron # |
move &checksignal256, jumpback # ->--|--+
jump sendsignal # | |
# | |
# | |
checksignal256: move 256, i # <---+--+
and recvsignal, i
equal 256, i
jump sendsignal256
jump checksignal512 # ->--+
# |
sendsignal256: equal nextneuron, 0 # <= if we're the last neuron in this signal chain
jump checksignaldo # <= do the action.
move recvsignal, sendsignaldata # |
move nextneuron, sendsignalneuron # |
move &checksignal512, jumpback # ->--|--+
jump sendsignal # | |
# | |
# | |
checksignal512: move 512, i # <---+--+
and recvsignal, i
equal 512, i
jump checksignaldo # 512 -- do the action anyway.
jump checksignalend
checksignaldo: move &checksignalend2, jumpback
jump doaction # on return, doaction is expected to clear (or set) signalstatus
checksignalend: move 0, signalstatus # reset signalstatus -- we only passed a signal
checksignalend2: move checksignalret, jumpback # return to caller.
jump [jumpback]
# ----------- Function: doaction() --------------------
# Clobbered but restored: jumpback
# Clobbered: recvsignal, signalstatus (by action 4 and 8)
doactionret: data 0 # caller's return address
# think of the following code like a switch-case statement that operates on a bitflag
# it is optimised for code size, not speed. (it saves 1 or 2 jump instructions on each case in the switch-case)
# at the expense of re-evaluating the entire switch-case from the beginning.
doaction: move jumpback, doactionret
and 127, recvsignal # mask only the bits we want...
doactionstart: move &doactionstart, jumpback # set this as the jumpback value
# because "doaction" will change "recvsignal"
# by substracting from recvsignal the action being done.
# (this saves some code -- but you cannot do this for "signal handling actions"
equal 0, recvsignal # are we done yet?
jump doactiondone
move 1, i
and recvsignal, i
equal 1, i
jump doaction1
move 2, i
and recvsignal, i
equal 2, i
jump doaction2
move 4, i
and recvsignal, i
equal 4, i
jump doaction4
move 8, i
and recvsignal, i
equal 8, i
jump doaction8
move 32, i
and recvsignal, i
equal 32, i
jump doaction32
move 64, i
and recvsignal, i
equal 64, i
jump doaction64
data 0 # buggy code? undefined action signal
# -- NB these subroutines of doaction() will return to doaction()'s switch-case statement
# via "jump [jumpback]" and not "return to caller" since that only occurs once (see above).
# --- Action 1: Create more neurons
doaction1: add -1, recvsignal # substract from recvsignal, so this action isn't repeated again.
move &linkneurons, jumpbackclone # make clone() fork and link the neurons
jump clone # jumpback is already set by doaction()
# --- Action 2: Move neuron.
doaction2: add -2, recvsignal
move &moveneuron, jumpbackclone # no need to set jumpback
jump clone
# --- Action 4: move 0, [random] (safe version)
doaction4: move 0, signalstatus # reset signalstatus here.
doaction4p1: equal 2, signalstatus # check signalstatus -- if this changes, we stop action4
jump stopaction4
doaction4p2: info 2, r
own [r]
jump doaction4p2
move 0, [r]
jump doaction4p1
stopaction4: jump doactiondone2 # return, but don't reset signalstatus
# --- Action 8: move 0, [random] (unsafe, faster)
doaction8: move 0, signalstatus # reset signalstatus here.
doaction8p1: equal 2, signalstatus # check signalstatus -- if this changes, we stop action8
jump stopaction8
info 2, r
move 0, [r]
jump doaction8p1
stopaction8: jump doactiondone2 # return, but don't reset signalstatus
# --- Action 32: Convert into an imp
doaction32: move doaction32, imp # no need for jump [jumpback]
imp: data 0
# --- Action 64: Terminate neuron
doaction64: data 0 # no need for jump [jumpback]
# --- Done?
doactiondone: move 0, signalstatus # reset signalstatus.
doactiondone2: move doactionret, jumpback
jump [jumpback] # return to caller
# ----------- Function: sendsignal() -------------------------------------------
# Clobbered: None.
sendsignal: equal 1337, [sendsignalneuron] # check target is a valid neuron by testing beginmarker
jump sendsignal2
data 0 # buggy code? terminate
sendsignal2: move sendsignalneuron, r
add 1, r # set r to point to "signalstatus"
sendsignal3: equal 0, [r] # simplified version.
jump sendsignal4
jump sendsignal3
sendsignal4: move 1, [r] # lock signalstatus
move r, i
add 1, i # set i to point to "signal"
move sendsignaldata, [i] # send signal
move 2, [r] # update signalstatus to completed
sendsignalend: jump [jumpback] # return to caller.
# ----------- Function: clone() ------------------------------------------
# Clobbered: None. (Also see polymorphic functions.)
# NB: "jumpbackclone" is a polymorphic function descriptor (i.e. function pointer or subclass method)
# which changes the behaviour of the clone code
clone: info 2, r
own [r] # check if 'r' overwrites into another neuron.
jump clone
move &neuronmarker, s # grab length of neuron (i could hardcode this, but i dont want to)
neg s, i
add &endvar, i
add 1, i
move r, endvar # save 'r'
add i, r # check if 'r + len' overwrites into another neuron.
own [r]
jump clone
move endvar, r # restore 'r'
copy: movei s, r
loop i, copy
move 1337, [endvar] # set neuron marker
jump [jumpbackclone] # jump to the polymorphic function -- either moveneuron() or linkneurons()
# --- Polymorphic jumpbackclone function: linkneurons() ---
# Clobbered: nextneuron
linkneurons: equal 0, nextneuron
jump linkneurons1
data 0 # buggy code? terminate
linkneurons1: move endvar, r # zero the bss
add 1, r # (1st var location)
move sizeofvars, i # (number of vars)
zerobss: move 0, [r]
add 1, r
loop i, zerobss
move endvar, nextneuron # set our "nextneuron" to point to the clone
move endvar, r
add 4, r
move &neuronmarker, [r] # set "prevneuron" in the cloned neuron to us.
add entrypointoffset, endvar # readjust endvar to entry point of cloned neuron
fork [endvar] # fork
jump [jumpback] # return to caller
# --- Polymorphic jumpbackclone function: moveneuron() ---
moveneuron: move prevneuron, r
add 3, r
move endvar, [r] # set previous neuron's "next neuron" to new address
move nextneuron, r
add 4, r
move endvar, [r] # set next neuron's "prev neuron" to new address
add entrypointoffset, endvar # this is just a clone_move() operation, so re-adjust entrypoint
jump [endvar] # and only jump (don't fork)
# ------------------------------------------------------------------------------------
endvar: data 0 # end variable -- used to mark end of neuron -- only to be used by clone().
# data/code below is NOT used -- just to look nice in the gui -- only first neuron has this.
neuronendmarker: move &neuronendmarker, neuronendmarker1
neuronendmarker1: data 0
The following corewars program is a statistically optimal program, which attacks a midpoint of a program, given the number of programs, coresize, average program size, and then their standard deviation. It’s just using pre-defined numbers, generated from a shell script…
r: data 0
coresize / nprogs = 273
avg prog size: 35.65
std dev: 31.49
thus, desired search points:
[ |1dev| (midpoint) |1dev| ]
make next midpoint here: ---------------------> ^^^^^^
midpoint = interval / 2 = 136
main: move 0, [r] # midpoint
add -31, r
move 0, [r] # left 1dev
add 62, r
move 0, [r] # right 1dev
add 74, r
jump main
Here is the corresponding shell script:
#!/bin/sh
# this calculates statistics from any list of numbers.
# in particular, it's used to calculate the number of lines for each
# corewars program.
# wc -l *.cw ... which is then parsed by cut -d ' ' -f [1,2,3,4,5,6]
# to get all the tokens, and then "grep -v" to remove the text,
# since the output of wc is not cut-friendly.
# expavg is actually the average from a previous calculation, but
# since this requires 2 passes, i couldn't be bothered. just
# alter it yourself for the 2nd pass.
r=0
sum=0
avg=0
numprogs=0
expavg=35
while read r ; do
numprogs=`expr $numprogs '+' 1`
avg=`expr $r '+' $avg`
r=`expr $r '-' $expavg`
s=`expr $r '*' $r`
sum=`expr $sum '+' $s`
done
echo "numprogs = $numprogs"
echo "avg = $avg / numprogs"
echo "dev = sqrt ( $sum / numprogs )"
ChatGPT having a gripe? I never thought I’d see this sight…
Thrown Under the Bus
— A poem about betrayal in the workplace
Thanks for throwing me under the bus, You're not someone that I should trust. You smiled at me and shook my hand, Then pulled the ground on which I'd stand.
You gave me tasks I'd never known, Then left me there to work alone. No helping hand, no time to learn, Just deadlines tight at every turn.
You knew the skills I did not hold, Yet watched the story still unfold. You set the bar beyond my reach, Then blamed me for what you didn't teach.
You called it failure, called it slow, But never showed me where to go. You questioned all the work I'd done, While moving goalposts one by one.
I asked for guidance, asked for time, You treated every plea as crime. And when the deadlines came and went, You questioned my commitment and intent.
You took my effort, took my pride, Then cast my explanations aside. You built a case from every flaw, And called it fair, and called it law.
Behind closed doors, you made your plan, To break the spirit of a working man. Not through support, or honest review, But finding fault in all I'd do.
You called it leadership, called it fair, But where was leadership when I needed you there? A title doesn't make you wise, Nor does the truth become your lies.
A manager's job is not to blame, Or turn a person's work to shame. It's helping people learn and grow, And teaching what they do not know.
You tried to make me doubt my worth, To make me question my place on earth. But here's the truth you cannot hide: I worked with honesty, and I tried.
I never claimed to know it all, I simply asked you not to let me fall. Yet rather than extend a hand, You chose to watch where I would land.
So keep your title, keep your chair, Keep pretending you were fair. You may control the words you say, But not the truth you pushed away.
For trust is earned, not simply due, And respect must work both ways too. You may have power to end my stay, But not the strength I've built along the way.
And when the dust has finally cleared, I'll know I faced the things I feared. You may have tried to make me fall, But your betrayal says more about you than me at all.
In computing, a buffer is meant to be a place of order — a defined space, a promise of containment. Data comes in, it fits neatly, it moves on. When it works, it’s invisible.
A buffer overflow happens when more data is written than the space was designed to hold. It’s a simple arithmetic mistake, yet it tells a deeper story: that expectations and reality have drifted apart. Somewhere, a promise was made that the system could handle it. It couldn’t.
The elegance of limits
Every buffer has boundaries. They aren’t failures of imagination; they’re expressions of discipline. Good code checks its inputs — not because it doubts the data, but because it respects the container.
The trouble begins when that respect erodes. A few extra bytes here, a little exception there — it all seems harmless. After all, the system hasn’t crashed yet. The logs are clean. Things appear to be running smoothly.
Until, suddenly, they aren’t.
Silent corruption
Overflows rarely announce themselves immediately. They don’t explode in a dramatic cascade of errors. Instead, they leak — a value overwritten here, a flag flipped there. Something subtle, deniable.
By the time the failure becomes visible, the origin is hard to find. Everyone points somewhere else in the code. It’s difficult to assign blame when the system’s integrity has been quietly compromised over time.
Sometimes the corruption spreads. Adjacent structures — those meant to hold something entirely different — begin to behave strangely. Functions misfire. Memory turns unreliable. Trust becomes guesswork.
The illusion of capacity
Developers often overestimate how much a buffer can hold. Maybe they assume the input will stay small. Maybe they’ve handled bigger loads before and assume it’ll be fine again. Maybe the warnings were commented out long ago — “temporary,” of course.
But capacity isn’t about confidence. It’s about measurement, and respect for constraints that aren’t negotiable. Once a buffer starts stretching to accommodate everything asked of it, something essential has already gone wrong.
Defensive design
Resilient systems anticipate excess. They install boundaries not as barriers, but as protections for what’s inside. They check lengths before trusting input. They reject what doesn’t fit. They log, they pause, they push back.
The best code doesn’t aspire to handle *everything*. It knows what it’s for, and stops there.
Recovery
After an overflow, cleanup is difficult. You can patch the code, harden the interface, maybe even redesign the structure. But the memory once corrupted can leave traces — artifacts of what used to be reliable.
Over time, systems that keep overflowing develop a kind of brittleness. Patches pile up. Documentation grows vague. No one remembers why certain limits exist, only that removing them “breaks something.”
Healthy systems are unglamorous. They validate, reject, and defer. They leave a little space unused — not wastefully, but wisely.
Epilogue
It’s easy to admire a program that takes on more than it was meant to, that runs hot and appears to handle it all. But real stability — the quiet kind — comes from knowing precisely where the edges are, and refusing to cross them.
Because when a system fails, it’s rarely because it didn’t have enough capacity. It’s because it didn’t respect the capacity it already had.
From the Systems Desk
Author: Anonymous (but probably someone who’s seen a few core dumps, both digital and otherwise).
Why quality matters over quantity when it comes to coding
When you create a piece of code, it’s like a cookie cutter template. You can replicate the software very easily, but if there’s an error in the template, the error propagates to every copy of the software.
You also end up with a bad reputation.
That’s why when you create code, quality must come first. Quantity (lines of code, features, etc) come second, because it doesn’t matter how many features or lines of code you have, if ONE single line of code has an error, that’s a potential show-stopper.
Here’s where DRUDGET’S cybersecurity software comes in. You might write sloppy C/C++ code where you don’t check for buffer overflows. It doesn’t matter, you pray the mitigations will catch it. It doesn’t. Unless you use MMU (memory management unit) based mitigations. That’s what DRUDGET’s heap buffer overflow protector uses.
Again, DRUDGET’s code wouldn’t be necessary if you wrote perfect code the first time around. DRUDGET catches other things too: memory leaks, deadlocks, all caught and solved; garbage collection for memory leaks, and deadlock detector plus real-time deadlock workaround.
Unfortunately though, none of this is useful for “critical” applications that demand life-safety first: DRUDGET’s software can only do so much. Embedded firmware running in some RTOS that is used in a medical device, or auto-pilot vehicle, needs to be written with QUALITY in mind.
That’s why DRUDGET also has a source code auditing service. We audit your source code to ensure it complies with standards and use automated tools, that we crafted ourselves, as well as manual intervention, to secure your source code.
(As an example using our auditing tool, we found a flaw in Apache: CVE-2023-31122. https://ubuntu.com/security/CVE-2023-31122 – CVSS 3 Severity Score of 7.5 “high”. I wanted to find a flaw in an open source application, and it took less than a minute to find it.)
So if you write C/C++ code, and your reputation is on the line, go visit DRUDGET (https://www.drudget.com.au) and send us your details. Our software makes QUALITY code easier to manufacture.
Previously I wrote why AGILE sucks. Now I write a piece on why AGILE doesn’t suck.
Godel
First off I start with a theorem based off Godel’s incompleteness theorem, which basically says:
Any theory T has a paradox P, and can’t be resolved unless a new theory T’ is made, which solves the paradox P but will have its own paradox P’.
That is because of Godel’s incompleteness theorem. Either the theory is inconsistent, or cannot prove everything; which idealises itself as a paradox.
Why AGILE doesn’t suck.
Imagine a computer program is a theory T. And that it should match the business case. However, there’s a paradox P, which the business case might need to meet and solve, and thus the computer program needs to change to T’ (knowing full well there’s another P’ that could be a problem.)
So AGILE capitalises on this by making changes favourable — being flexible and not being fixed in a contract allows you to manipulate theories: T -> T’ -> T” -> T”’ and so on; with corresponding paradox “Ps” i.e. P -> P’ -> P” -> P”’.
However, this is where AGILE still sucks.
Why AGILE still sucks.
If you manipulate theories, you will have a bunch of paradoxes which need to be solved by each theory. If it’s unsolved, that’s technical debt. So you have to refactor after each addition of a new theory, to ensure you eliminate the paradoxes and only have one SINGLE final paradox.
But we are human.
So I guess that’s the deciding factor. We are human, and our business cases are always expanding in scope. Translated, this means our theories will always expand into some new theory.
So this is why AGILE wins in the end. If done properly, with refactoring and eliminating paradoxes, it will have the better outcome.
However, when I run a business, and I sure as hell won’t develop more code than I need to unless you pay me a subscription fee…
Because creating code isn’t free.
But, then, that’s where the generative AI is supposed to come in and tell me that you should code for free because otherwise you’ll be replaced with AI and its “Large Language Models”.
But they’re flawed. We know, because they’re unable to solve even the simplest paradoxes.
Because paradoxes are hard. And they’re dependent on the environment — e.g. chicken or egg paradox, which came first? We can’t tell just by looking at that sentence, but we know, given biology, that a bird that looked like a chicken laid an egg which had genetic mutations which gave rise to a chicken. (Note: It doesn’t resolve the paradox of which came first, the bird or the egg; as you can see, it just shifts the paradox into a larger scope paradox.)
Generative AI is not even close to solving paradoxes like that, because it doesn’t experience the human world. The real world. The reality.
Nature defines how paradoxes are solved.
I was working on Riemann’s hypothesis by the way…
And I realised the reason why the hypothesis exists is because it’s trying to solve a paradox.
We’re going to need a new theory T’ (and not just ZFC set theory) to solve it. However, that’s the problem. Riemann’s hypothesis tackles the theory of theories and paradoxes, because it’s attempting to solve a problem relating to prime numbers, and that’s part of the problem, since there’s an infinite number of primes, and ZFC says you can’t prove infinity (since you can’t count to infinity), and that itself is a paradox.
However, attacking RH using modular forms is something interesting. Imagine being able to wrap up all the prime numbers in a modular form. That would allow you to brute force all possibilities and solve RH.
However, there is no pattern to prime numbers, as it appears to be randomly distributed as it gets larger. So … modular forms is probably not a solution, in fact, it’s probably a paradox in modular forms as well.
The paradox P just keeps in escaping into every new theory T’.
There is always going to be one final paradox anyway.
So, hence, AGILE still sucks.
(Edit: I mentioned “modular forms” and I only now realise that was a mistake. I meant finite field, which RH has been solved for, but whether this applies generally is another question.)
For example, if I was given a ticket for a requirement, where I say y = x + 2.
Then the code I write:
“`
int func(int x)
{
return x + 2;
}
“`
Now I’m given a new requirement, where it’s supposed to double the input, and chain that up with the original function.
“`
int func2(int x)
{
return 2 * func(x);
}
“`
Now I’m given a third requirement, which is to substract 4 from the input, and it’s supposed to chain up to func2.
“`
int func3(int x)
{
return func2(x) – 4;
}
“`
Now I’m given a fourth requirement, which is to substract 2x of the input from the chained function.
“`
int func4(int x)
{
return func3(x) – (2 * x);
}
“`
If I put all the code together, and run it:
“`
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int func(int x)
{
return x + 2;
}
int func2(int x)
{
return 2 * func(x);
}
int func3(int x)
{
return func2(x) – 4;
}
int func4(int x)
{
return func3(x) – (2 * x);
}
int main()
{
int i;
for (i = 0; i < 256; i++) {
printf(“%dn”, func4(i));
}
}
“`
I will get all zeroes.
If I simply refactored the code, I would get:
“`
int refactored(int x)
{
return 0;
}
“`
This is why AGILE development sucks, and never gives the simplest possible code to do a particular task, unless you refactor it constantly.
Because you will always get “new” requirements, and some of them are conflicting with each other, or cancel each other out, and if you don’t refactor to remove these conflicting requirements, they linger in code as bloat.
Strictly speaking, waterfall designers “know” what they want and would’ve gone straight towards “refactored(int x)”, if they have enough insight (and deep knowledge of the task). This is why they still can’t be beaten, when it comes to designing good software or hardware.
AGILE development is for babies who don’t know what they’re doing, and they’re guessing every step of the way.