Skip to content

Commit 09eb027

Browse files
committed
remove intended bugs
1 parent d7e695b commit 09eb027

File tree

7 files changed

+73
-214
lines changed

7 files changed

+73
-214
lines changed

game.c

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,16 @@ piece_t new_piece(shape_t s) {
184184
game_t *new_game(game_t *g, char *gname) {
185185
if (!g) {
186186
g = calloc(sizeof(game_t), 1);
187+
} else {
188+
memset(g, 0, sizeof(game_t));
187189
}
190+
188191
if (!gname) {
189192
gname = "";
190193
}
194+
/* fixme add to game struct */
195+
memset(bag, 0, sizeof bag);
196+
bag_fullness = 7;
191197
g->p = new_piece(0);
192198
for (int i = 0; i < PIECE_PREVIEWS; i++) {
193199
g->preview[i] = rand_shape();
@@ -196,14 +202,19 @@ game_t *new_game(game_t *g, char *gname) {
196202
return g;
197203
}
198204

199-
/* Return true if p overlaps with any placed piece in g
200-
* BUG: The piece can be above the board, so the returned value can be an
201-
* out-of-bounds read */
202-
/* Other than intentional leaks, do not call unless you're sure the piece is on
203-
* the board */
205+
/* Return true if p overlaps with any placed piece in g */
204206
shape_t check_dead(game_t *g, const piece_t *const p) {
205207
pos_t cells[4];
206208
get_cells(*p, cells);
209+
int min_x = min4(cells[0].x, cells[1].x, cells[2].x, cells[3].x);
210+
int max_x = max4(cells[0].x, cells[1].x, cells[2].x, cells[3].x);
211+
int min_y = min4(cells[0].y, cells[1].y, cells[2].y, cells[3].y);
212+
int max_y = max4(cells[0].y, cells[1].y, cells[2].y, cells[3].y);
213+
214+
if (min_x < 0 || min_y < 0 || max_x >= BOARD_W || max_y >= BOARD_H) {
215+
return P_WALL;
216+
}
217+
207218
for (int i = 0; i < 4; i++) {
208219
shape_t on_board = g->board[cells[i].y][cells[i].x];
209220
if (on_board != P_NONE) {
@@ -344,24 +355,20 @@ shape_t print_game(game_t *g) {
344355
* hop over the cookie and rbp, so i copy the game pointer after the bad array
345356
* instead. */
346357
int clear_lines_write_shape(game_t *g) {
347-
volatile struct {
348-
score_t scores[5];
349-
game_t *g2;
350-
} tsp_bug = {{0, 100, 300, 500, 800}, g};
351-
358+
score_t scores[6] = {0, 100, 300, 500, 800, 1000};
352359
int should_clear = 0;
353360
int rowcnt[BOARD_H] = {0}; /* row fullness */
354361

355362
pos_t curr_cells[4];
356-
get_cells(tsp_bug.g2->p, curr_cells);
363+
get_cells(g->p, curr_cells);
357364

358365
for (int i = 0; i < 4; i++) {
359366
/* count the current piece */
360367
rowcnt[curr_cells[i].y]++;
361368
}
362369
for (int y = 0; y < BOARD_H; y++) {
363370
for (int x = 0; x < BOARD_W; x++) {
364-
if (tsp_bug.g2->board[y][x] != P_NONE) {
371+
if (g->board[y][x] != P_NONE) {
365372
rowcnt[y]++;
366373
}
367374
}
@@ -374,8 +381,7 @@ int clear_lines_write_shape(game_t *g) {
374381
if (!should_clear) {
375382
/* writing the shape to the board */
376383
for (int i = 0; i < 4; i++) {
377-
tsp_bug.g2->board[curr_cells[i].y][curr_cells[i].x] =
378-
tsp_bug.g2->p.s;
384+
g->board[curr_cells[i].y][curr_cells[i].x] = g->p.s;
379385
}
380386
return 0;
381387
}
@@ -384,51 +390,45 @@ int clear_lines_write_shape(game_t *g) {
384390
* triple gives score_i = 5 */
385391
int score_i = 0;
386392

387-
if (tsp_bug.g2->p.s == P_T) {
393+
if (g->p.s == P_T) {
388394
/* puts("is T"); */
389-
piece_t new_p = tsp_bug.g2->p;
390-
new_p.pos.y = tsp_bug.g2->p.pos.y - 1;
395+
piece_t new_p = g->p;
396+
new_p.pos.y = g->p.pos.y - 1;
391397

392398
pos_t cells[4];
393399
get_cells(new_p, cells);
394400
int min_y = min4(cells[0].y, cells[1].y, cells[2].y, cells[3].y);
395401
if (min_y >= 0 && check_dead(g, &new_p)) {
396-
/* BUG: If we find a t-spin (last piece was a t piece AND it cannot
397-
* be moved up one spot AND we clear at least one line) we try to
398-
* boost the score by setting score_i. Since the line clear counting
399-
* happens after regardless, we get max points for a t-spin single,
400-
* and two possible leaks for 2 and 3 cleared lines respectively.
401-
*/
402-
score_i = 3;
402+
score_i = 2;
403403
}
404404
}
405405

406406
/* writing the shape to the board */
407407
for (int i = 0; i < 4; i++) {
408-
tsp_bug.g2->board[curr_cells[i].y][curr_cells[i].x] = tsp_bug.g2->p.s;
408+
g->board[curr_cells[i].y][curr_cells[i].x] = g->p.s;
409409
}
410410

411411
int src_y = BOARD_H - 1;
412412
/* I suppose an overflow when shifting things down could also be good */
413413
for (int dst_y = BOARD_H - 1; dst_y >= 0; dst_y--, src_y--) {
414414
if (src_y < 0) { /* nothing to shift; copy a blank line */
415415
for (int x = 0; x < BOARD_W; x++) {
416-
tsp_bug.g2->board[dst_y][x] = P_NONE;
416+
g->board[dst_y][x] = P_NONE;
417417
}
418418
} else {
419419
while (rowcnt[src_y] == BOARD_W && src_y >= 0) { /* clear */
420420
src_y--;
421421
score_i++;
422422
}
423423
for (int x = 0; x < BOARD_W; x++) {
424-
tsp_bug.g2->board[dst_y][x] = tsp_bug.g2->board[src_y][x];
424+
g->board[dst_y][x] = g->board[src_y][x];
425425
}
426426
}
427427
}
428428

429429
/* printf("score_i: %d\n", score_i); */
430430
fflush(stdout);
431-
return tsp_bug.scores[score_i];
431+
return scores[score_i];
432432
}
433433

434434
/* Shift the queue forward, filling in the new spot, and throwing away curr.

makefile

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
DEBUG := 0
22

33
CC := gcc
4-
CCFLAGS := -I .
4+
CCFLAGS := -I . -fcf-protection=full -fstack-protector-all -O2
55
BFLAGS := --no-lines
66

77
ifeq ($(DEBUG), 1)
8-
CCFLAGS += -g -Wall -Wextra -Wpedantic
8+
CCFLAGS += -g -Wall -Wextra -Wpedantic -fsanitize=address -fsanitize=undefined -fsanitize=signed-integer-overflow -fsanitize-undefined-trap-on-error
99
# CCFLAGS += -fno-pie -no-pie
1010
# BFLAGS += -Wcounterexamples
11-
else
12-
CCFLAGS += -O1
1311
endif
1412

1513
OUTDIR := out
@@ -52,39 +50,32 @@ $(OUTDIR)/tetrominobot: tetrominobot.c $(OUTDIR)/game $(OUTDIR)/robot $(OUTDIR)/
5250
tx: $(OUTDIR)/tx
5351

5452
tx_clean:
55-
rsync -rva --exclude $(OUTDIR) --exclude .git . $(VM_IP):tbot
56-
ssh -A $(VM_IP) "cd tbot && make clean"
53+
rsync -rva --exclude $(OUTDIR) --exclude .git . $(VM_IP):tbot2
54+
ssh -A $(VM_IP) "cd tbot2 && make clean"
5755

5856
$(OUTDIR)/tx: robot.c game.c tetrominobot.c |$(OUTDIR)
59-
rsync -rva --exclude $(OUTDIR) --exclude .git . $(VM_IP):tbot
60-
ssh -A $(VM_IP) "cd tbot && make handouts"
61-
scp -r $(VM_IP):~/tbot/$(OUTDIR)/* ./$(OUTDIR)/
62-
# scp $(VM_IP):~/tbot/$(OUTDIR)/tetrominobot $(OUTDIR)/tx
57+
rsync -rva --exclude $(OUTDIR) --exclude .git . $(VM_IP):tbot2
58+
ssh -A $(VM_IP) "cd tbot2 && make handouts"
59+
scp -r $(VM_IP):~/tbot2/$(OUTDIR)/* ./$(OUTDIR)/
6360

64-
D_IMG := tbot_img
65-
D_CON := tbot_container
61+
D_IMG := tbot2_img
62+
D_CON := tbot2_container
6663
D_BUILD := /build
6764

6865
$(HDIR):
6966
mkdir -p $(HDIR)
7067

71-
handouts: robot.c game.c tetrominobot.c player-manual.org makefile $(OUTDIR)/docker_tbot_cont |$(HDIR)
68+
handouts: robot.c game.c tetrominobot.c player-manual.org makefile $(OUTDIR)/docker_tbot2_cont |$(HDIR)
7269
docker restart $(D_CON)
7370
docker cp . $(D_CON):$(D_BUILD)
7471
docker exec $(D_CON) /bin/bash -c 'cd $(D_BUILD) && make'
75-
docker exec $(D_CON) /bin/bash -c 'cd $(D_BUILD) && make'
76-
ifneq ($(DEBUG),1)
7772
docker exec $(D_CON) /bin/bash -c 'strip --strip-all $(D_BUILD)/$(OUTDIR)/tetrominobot'
78-
endif
79-
docker cp $(D_CON):$(D_BUILD)/$(OUTDIR)/tetrominobot $(HDIR)
8073
docker cp $(D_CON):$(D_BUILD)/$(OUTDIR)/tetrominobot $(HDIR)
81-
docker cp $(D_CON):/lib/x86_64-linux-gnu/libc.so.6 $(HDIR)
82-
docker cp $(D_CON):/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 $(HDIR)
8374
docker stop $(D_CON)
8475
cp ./player-manual.org $(HDIR)
8576
cp ./simple.tbot $(HDIR)
8677

87-
$(OUTDIR)/docker_tbot_image: dockerfile |$(OUTDIR)
78+
$(OUTDIR)/docker_tbot2_image: dockerfile |$(OUTDIR)
8879
touch $@
8980
# clean
9081
docker stop $(D_CON) || true
@@ -93,7 +84,7 @@ $(OUTDIR)/docker_tbot_image: dockerfile |$(OUTDIR)
9384
# build
9485
docker build . -t $(D_IMG) --platform=linux/amd64
9586

96-
$(OUTDIR)/docker_tbot_cont: $(OUTDIR)/docker_tbot_image |$(OUTDIR)
87+
$(OUTDIR)/docker_tbot2_cont: $(OUTDIR)/docker_tbot2_image |$(OUTDIR)
9788
touch $@
9889
# clean
9990
docker stop $(D_CON) || true

parse.y

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ cond_if
106106
}
107107
}
108108
| fcall
109-
| PRINT '(' exp ')' { printf("%ld\n", $3); }
109+
| PRINT '(' exp ')' { if (cond_active) { printf("%ld\n", $3); } }
110110
/* or macro call? */
111111
;
112112

@@ -174,7 +174,8 @@ semicolon.opt:
174174

175175
void yyerror (game_t *g, tbot_t *t, char *s) {
176176
(void)g;
177-
fprintf (stderr, "at pos %ld (\"%c\"): %s\n", t->ppos, t->prog[t->ppos], s);
177+
char checked = strlen(t->prog) > t->ppos ? t->prog[t->ppos] : '?';
178+
fprintf(stderr, "at pos %ld (\"%c\"): %s\n", t->ppos, checked, s);
178179
exit(1);
179180
}
180181

readme.org

Lines changed: 15 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1+
* tbot2
2+
see tetrominobot; this is hopefully the bugless version. tell people to use the pwn binary for
3+
practice, but the one running on the server has been changed for this challenge, and no funny
4+
business is allowed for this one anyway. i'll try to make this one as secure as
5+
possible. suggestions are welcome.
6+
7+
note we still want to run with the same libc since the rand() piece generator is deterministic with
8+
the input, and this allows people to test locally using the pwn binary
9+
10+
Going with 30k iterations for now. How many points for a flag? 8k?
11+
12+
30k cycles * (1 piece dropped per ~30 cycles) * (4 cells per piece dropped) * (1 line cleared per
13+
10 cells) * (100 point mimimum per line cleared) = ~40k points estimated possible
14+
115
* tetrominobot
2-
You can call yourself an AI engineer after this one
316

417
** details
518
The program allows the player to create little tetris bots. minus pwn, the intention would be to get
@@ -23,86 +36,8 @@ There won't be loops beyond the outer loop, but workarounds using counters are p
2336

2437
todo add grammar
2538

26-
** exploit
27-
28-
It might suffice to do either bug 1 or bug 2 interchangably (1 probably being more useful; 2 is
29-
mostly there because having a t-spin leak is funny). 3 should be mandatory.
30-
31-
*** optional bug: srand (piece generator) control
32-
Intentionally lazy rng so the player can get deterministic and known pieces with a little
33-
effort. makes bugs 1+2 a lot easier.
34-
- Before running the bot we call srand(x) where x is a small unsigned number that is the sum of all
35-
bytes in the player input
36-
- The parser stops at an EOF character and so the user can put whatever they like after one.
37-
- Like in the real game, pieces are repeatedly drawn from a bag of 7, so this just controls the
38-
permutations (the player can't just ask for I pieces). however it makes things much easier to
39-
programmatically generate the after-eof garbage and get the same sequence of pieces every time, no
40-
matter what they are.
41-
42-
I'll use this bug for now to try and make intended bugs more attractive than any unintended ones.
43-
however, it would make for an interesting challenge to use real randomness.
44-
45-
*** bug 1: libc leak through the ceiling
46-
- Like in the real game, pieces existing above the visible board does not kill you
47-
- you die when a new piece can't spawn (usually in the top-middle of the board)
48-
- An address or some other interesting data is stored above the board in memory
49-
- libc? address of game memory for a ropchain?
50-
- Piece movement functions return 0 for success or a number describing what the piece ran into
51-
- Like in the real game, there is no "move up" function, but if you are rotating a piece close to
52-
some obstacles, there is a bunch of "kick" positions that are tried, and many of these positions
53-
bump the rotated piece up
54-
55-
*** bug 2: base addr leak with a t-spin
56-
- the score delta when clearing n lines is array[n] where array is 4 constant ints stored on the
57-
stack
58-
- t-spins generally give a player more points than the raw amount of lines cleared
59-
- this is when the piece dropped was a T and the T is "under" some existing pieces
60-
- it is possible to clear 1, 2, or 3 lines with a t-spin
61-
- have the can't-move-T-up check set n=3 (maybe 4? maybe long ints?). Then have the line clearing
62-
check increment it with additional lines cleared
63-
- score will increment by whatever interesting data lies after the array. player can access the
64-
score in their bot.
65-
66-
decided to go with 2 lines good enough for a leak. i don't even know how to do a t-spin triple lol
67-
68-
*** bug 3: arbitrary call using bad debug mode and buggy cmdline arg parser
69-
- bot name can be entered through argv, or through an initial fgets if not found.
70-
- Debug mode can only be enabled thru argv
71-
- everything is put together, so any bot name starting with - and containing d enables debug
72-
- debug mode allows for an extra game function and adds it to the end of the game function table in
73-
the null spot
74-
- where the null spot was previously used as the end indicator
75-
- the player-writable memory is allocated right after the gfunctable
76-
- gfunctable is a record of char *name and func *s
77-
- player will need to
78-
- call a predetermined string in the program
79-
- find the pointer (hard) to this string and put this in mem[0] (easy)
80-
- find a pointer (hard) to whatever they'd like and write it to mem[1] (easy)
81-
- probably write a rop chain? onegadget might be possible, don't really have time to check
82-
- This is all made easier by the fact the player can re-run the program with a different bot until
83-
they quit or things segfault.
84-
85-
this seems convoluted but i'm imagining it would be pretty easy to be suspicious of the f*table and
86-
the player memory being so close together. i just need to make sure this doesn't open up any boring
87-
ways of finding good addresses (please use the ceiling bug and/or the t-spin bug)
88-
89-
90-
current working bug demo minus pie which calls rot_l by its address:
91-
92-
~{ mem[0] = 4219153; mem[1] = 4207180; call(help) }~
93-
94-
9539
** flag
96-
- maple{tell the marketing department that we use 99% fewer CPU cycles than competing apps}
97-
98-
99-
* todo
100-
in descending order of importance
101-
102-
- piece timeout?
103-
- ask people to debug cmdline args
104-
- print should only do so when debugging
105-
- test game functions
40+
- maple{?}
10641

10742

10843
* building

0 commit comments

Comments
 (0)