Bug#1034636: unblock: sgt-puzzles/20230122.806ae71-2
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: sgt-puzzles@packages.debian.org
Control: affects -1 + src:sgt-puzzles
Please unblock package sgt-puzzles
The changes for this look large, but most patches only change a few
lines. Combined diffstat for the new patches is:
blackbox.c | 2 +
bridges.c | 38 +++++++++++++++++++------
cube.c | 36 +++++++++++++++++++++---
dominosa.c | 6 ++--
filling.c | 2 +
flood.c | 45 +++++++++++++++++++-----------
galaxies.c | 15 ++++++----
inertia.c | 8 ++---
keen.c | 11 ++++++-
latin.c | 47 ++++++++++++++++++++-----------
latin.h | 9 ++++--
loopy.c | 16 ++++++++--
magnets.c | 4 ++
map.c | 2 -
midend.c | 14 ++++++---
mines.c | 22 ++++++++++++++
mosaic.c | 22 +++++++-------
net.c | 3 ++
netslide.c | 4 +-
palisade.c | 36 ++++++++++--------------
pattern.c | 5 ++-
pearl.c | 78 +++++++++++++++++++++++++++++++++++++++--------------
pegs.c | 11 ++++++-
random.c | 4 +-
range.c | 5 ++-
rect.c | 2 +
slant.c | 2 +
solo.c | 2 -
tents.c | 22 ++++++++++++--
tracks.c | 9 +++---
twiddle.c | 8 +++--
undead.c | 6 ++--
unequal.c | 44 ++++++++++++++++-------------
unfinished/group.c | 15 +++++-----
unruly.c | 2 +
untangle.c | 4 ++
36 files changed, 383 insertions(+), 178 deletions(-)
[ Reason ]
Fix security flaws in game loading. Also fix one non-security
crasher.
[ Impact ]
Loading a crafted game description or save file could have security
impacts up to and including arbitrary code execution.
[ Tests ]
There are no automated tests for this package.
I have tested that:
1. Each puzzle is playable to completion.
2. Each puzzle can generate and start a game from each of its
size/shape/difficulty presets.
3. Each puzzle can generate 10 random game save files and load them
without error.
[ Risks ]
As I have cherry-picked fixes from upstream, there is a possibility
that I missed some dependency for the fixes. However, I did review
all the upstream changes.
This is a leaf package.
[ Checklist ]
[X] all changes are documented in the d/changelog
[X] I reviewed all changes and I approve them
[X] attach debdiff against the package in testing
[ Other info ]
unblock sgt-puzzles/20230122.806ae71-2
diff -Nru sgt-puzzles-20230122.806ae71/debian/changelog sgt-puzzles-20230122.806ae71/debian/changelog
--- sgt-puzzles-20230122.806ae71/debian/changelog 2023-01-24 03:09:26.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/changelog 2023-04-16 21:19:11.000000000 +0200
@@ -1,3 +1,69 @@
+sgt-puzzles (20230122.806ae71-2) unstable; urgency=medium
+
+ * Fix various security issues in game loading (Closes: #1034190):
+ - Black Box: reject negative ball counts in game_params.
+ - Add validate_params bounds checks in a few more games.
+ - Don't allow Bridges games with < 2 islands
+ - Forbid moves that fill with the current colour in Flood
+ - Cleanly reject ill-formed solve moves in Flood
+ - Don't segfault on premature solve moves in Mines
+ - Limit number of mines in Mines game description
+ - Validate the number of pegs and holes in a Pegs game ID
+ - Mines: forbid moves that flag or unflag an exposed square
+ - Mines: Don't check if the player has won if they've already lost
+ - Avoid invalid moves when solving Tracks
+ - Fix move validation in Netslide
+ - Tighten validation of Tents game descriptions
+ - Dominosa: require the two halves of a domino to be adjacent
+ - Forbid lines off the grid in Pearl
+ - Tolerate incorrect solutions in Inertia
+ - Palisade: replace dfs_dsf() with a simple iteration.
+ - latin_solver_alloc: handle clashing numbers in input grid.
+ - Pearl: fix assertion failure on bad puzzle.
+ - Pearl: fix bounds check in previous commit.
+ - Unequal: Don't insist that solve moves must actually solve
+ - Range: Don't fail an assertion on an all-black board
+ - Limit width and height to SHRT_MAX in Mines
+ - Mines: Add assertions to range-check conversions to short
+ - Unequal: fix sense error in latin_solver_alloc fix.
+ - Forbid impossible moves in Bridges
+ - Forbid game descriptions with joined islands in Bridges
+ - Check state is valid at the end of a move in Pearl
+ - Cleanly reject more ill-formed solve moves in Flood
+ - Don't allow moves that change the constraints in Unequal
+ - Fix memory leaks in Keen's validate_desc()
+ - Remember to free the actual_board array in Mosaic
+ - Don't leak grids in Loopy's validate_desc()
+ - Remember to free the to_draw member from Net's drawstate
+ - Undead: check the return value of sscanf() in execute_move()
+ - Don't leak duplicate edges in Untangle
+ - Remember to free the numcolours array from Pattern's drawstate
+ - Free new game_state properly in Mosaic's execute_move()
+ - Twiddle: don't read off the end of parameter strings ending 'm'
+ - Loopy: free the grid description string if it's invalid
+ - Mosaic: don't duplicate the description being validated
+ - Avoid division by zero in Cube grid-size checks
+ - Validate that save file values are ASCII (mostly)
+ - More validation of solve moves in Flood
+ - Make sure that moves in Flood use only valid colours
+ - Tighten grid-size limit in Mines
+ - Tracks: set drag_s{x,y} even if starting off-grid
+ - Undead: be a bit more careful about sprintf buffer sizes
+ - Fix memory leak in midend_game_id_int()
+ - Flood: don't read off the end of some parameter strings
+ - Be more careful with type of left operand of <<
+ - Map: reduce maximum size
+ - Correctly handle some short save files
+ - Inertia: insist that solutions must be non-empty
+ - Galaxies: fix recursion depth limit in solver.
+ - Correct a range check in Magnets' layout verification
+ - Magnets: add a check that magnets don't wrap between lines
+ - Net: assert that cx and cy are in range in compute_active()
+ - Don't allow zero clues in Pattern
+ * Solo: cope with pencil marks when tilesize == 1 (Closes: #905852)
+
+ -- Ben Hutchings <benh@debian.org> Sun, 16 Apr 2023 21:19:11 +0200
+
sgt-puzzles (20230122.806ae71-1) unstable; urgency=medium
* New upstream version:
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0001-Black-Box-reject-negative-ball-counts-in-game_params.patch sgt-puzzles-20230122.806ae71/debian/patches/0001-Black-Box-reject-negative-ball-counts-in-game_params.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0001-Black-Box-reject-negative-ball-counts-in-game_params.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0001-Black-Box-reject-negative-ball-counts-in-game_params.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,36 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 22 Jan 2023 08:54:06 +0000
+Subject: [PATCH 001/159] Black Box: reject negative ball counts in
+ game_params.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=5cac6a09c4db2b7e05c3e8dfd8920e2cdd1b8b03
+Bug-Debian: https://bugs.debian.org/1034190
+
+You can inject one via a game desc string such as "10x10M5m-1", and
+it's clearly silly.
+
+_Zero_ balls, on the other hand, are a perfectly fine number: there's
+nothing incoherent about a BB puzzle in which the possible numbers of
+balls vary from (say) 0 to 5 inclusive, so that part of the challenge
+is to work out as efficiently as possible whether there are even any
+balls at all.
+
+(We only need to check minballs, because once we know minballs >= 0,
+the subsequent check ensures that maxballs >= minballs, and hence, by
+transitivity, maxballs >= 0 too.)
+---
+ blackbox.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/blackbox.c b/blackbox.c
+index c93678a5..22ef4cb8 100644
+--- a/blackbox.c
++++ b/blackbox.c
+@@ -192,6 +192,8 @@ static const char *validate_params(const game_params *params, bool full)
+ * types, and could be worked around if required. */
+ if (params->w > 255 || params->h > 255)
+ return "Widths and heights greater than 255 are not supported";
++ if (params->minballs < 0)
++ return "Negative number of balls";
+ if (params->minballs > params->maxballs)
+ return "Minimum number of balls may not be greater than maximum";
+ if (params->minballs >= params->w * params->h)
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0002-Add-validate_params-bounds-checks-in-a-few-more-game.patch sgt-puzzles-20230122.806ae71/debian/patches/0002-Add-validate_params-bounds-checks-in-a-few-more-game.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0002-Add-validate_params-bounds-checks-in-a-few-more-game.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0002-Add-validate_params-bounds-checks-in-a-few-more-game.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,137 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 22 Jan 2023 09:30:57 +0000
+Subject: [PATCH 002/159] Add validate_params bounds checks in a few more
+ games.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=b907e278751b740da7b9dc00c0cbdb93e7498919
+Bug-Debian: https://bugs.debian.org/1034190
+
+Ben tells me that his recent work in this area was entirely driven by
+fuzzing: he added bounds checks in validate_params when the fuzzer had
+managed to prove that the lack of them allowed something buggy to
+happen.
+
+It seemed worth doing an eyeball-review pass to complement that
+strategy, so in this commit I've gone through and added a few more
+checks that restrict the area of the grid to be less than INT_MAX.
+
+Notable in this commit: cube.c had to do something complicated because
+in the triangular-grid modes the area isn't calculated as easily as
+w*h, and Range's existing check that w+h-1 < SCHAR_MAX is sufficient
+to rule out w*h being overlarge _but_ should be done before w*h is
+ever computed.
+---
+ cube.c | 24 ++++++++++++++++++++++++
+ filling.c | 2 ++
+ range.c | 2 +-
+ rect.c | 2 ++
+ slant.c | 2 ++
+ unruly.c | 2 ++
+ 6 files changed, 33 insertions(+), 1 deletion(-)
+
+diff --git a/cube.c b/cube.c
+index 32b7343f..21df46d3 100644
+--- a/cube.c
++++ b/cube.c
+@@ -542,12 +542,36 @@ static const char *validate_params(const game_params *params, bool full)
+ if (params->solid < 0 || params->solid >= lenof(solids))
+ return "Unrecognised solid type";
+
++ if (params->d1 < 0 || params->d2 < 0)
++ return "Grid dimensions may not be negative";
++
+ if (solids[params->solid]->order == 4) {
+ if (params->d1 <= 1 || params->d2 <= 1)
+ return "Both grid dimensions must be greater than one";
++ if (params->d2 > INT_MAX / params->d1)
++ return "Grid area must not be unreasonably large";
+ } else {
+ if (params->d1 <= 0 && params->d2 <= 0)
+ return "At least one grid dimension must be greater than zero";
++
++ /*
++ * Check whether d1^2 + d2^2 + 4 d1 d2 > INT_MAX, without overflow:
++ *
++ * First check d1^2 doesn't overflow by itself.
++ *
++ * Then check d2^2 doesn't exceed the remaining space between
++ * d1^2 and INT_MAX.
++ *
++ * If that's all OK then we know both d1 and d2 are
++ * individually less than the square root of INT_MAX, so we
++ * can safely multiply them and compare against the
++ * _remaining_ space.
++ */
++ if ((params->d1 > INT_MAX / params->d1) ||
++ (params->d2 > (INT_MAX - params->d1*params->d1) / params->d2) ||
++ (params->d1*params->d2 > (INT_MAX - params->d1*params->d1 -
++ params->d2*params->d2) / params->d2))
++ return "Grid area must not be unreasonably large";
+ }
+
+ for (i = 0; i < 4; i++)
+diff --git a/filling.c b/filling.c
+index 92939a14..7dec6560 100644
+--- a/filling.c
++++ b/filling.c
+@@ -188,6 +188,8 @@ static const char *validate_params(const game_params *params, bool full)
+ {
+ if (params->w < 1) return "Width must be at least one";
+ if (params->h < 1) return "Height must be at least one";
++ if (params->w > INT_MAX / params->h)
++ return "Width times height must not be unreasonably large";
+
+ return NULL;
+ }
+diff --git a/range.c b/range.c
+index d6d17ebb..a845b824 100644
+--- a/range.c
++++ b/range.c
+@@ -911,8 +911,8 @@ static const char *validate_params(const game_params *params, bool full)
+ int const w = params->w, h = params->h;
+ if (w < 1) return "Error: width is less than 1";
+ if (h < 1) return "Error: height is less than 1";
++ if (w > SCHAR_MAX - (h - 1)) return "Error: w + h is too big";
+ if (w * h < 1) return "Error: size is less than 1";
+- if (w + h - 1 > SCHAR_MAX) return "Error: w + h is too big";
+ /* I might be unable to store clues in my puzzle_size *grid; */
+ if (full) {
+ if (w == 2 && h == 2) return "Error: can't create 2x2 puzzles";
+diff --git a/rect.c b/rect.c
+index e2141fda..51150184 100644
+--- a/rect.c
++++ b/rect.c
+@@ -218,6 +218,8 @@ static const char *validate_params(const game_params *params, bool full)
+ {
+ if (params->w <= 0 || params->h <= 0)
+ return "Width and height must both be greater than zero";
++ if (params->w > INT_MAX / params->h)
++ return "Width times height must not be unreasonably large";
+ if (params->w*params->h < 2)
+ return "Grid area must be greater than one";
+ if (params->expandfactor < 0.0F)
+diff --git a/slant.c b/slant.c
+index 25af496d..f7070d79 100644
+--- a/slant.c
++++ b/slant.c
+@@ -226,6 +226,8 @@ static const char *validate_params(const game_params *params, bool full)
+
+ if (params->w < 2 || params->h < 2)
+ return "Width and height must both be at least two";
++ if (params->w > INT_MAX / params->h)
++ return "Width times height must not be unreasonably large";
+
+ return NULL;
+ }
+diff --git a/unruly.c b/unruly.c
+index 7a5512ea..0a9403eb 100644
+--- a/unruly.c
++++ b/unruly.c
+@@ -286,6 +286,8 @@ static const char *validate_params(const game_params *params, bool full)
+ return "Width and height must both be even";
+ if (params->w2 < 6 || params->h2 < 6)
+ return "Width and height must be at least 6";
++ if (params->w2 > INT_MAX / params->h2)
++ return "Width times height must not be unreasonably large";
+ if (params->unique) {
+ static const long A177790[] = {
+ /*
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0006-Don-t-allow-Bridges-games-with-2-islands.patch sgt-puzzles-20230122.806ae71/debian/patches/0006-Don-t-allow-Bridges-games-with-2-islands.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0006-Don-t-allow-Bridges-games-with-2-islands.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0006-Don-t-allow-Bridges-games-with-2-islands.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,58 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 00:45:38 +0000
+Subject: [PATCH 006/159] Don't allow Bridges games with < 2 islands
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=a98ac4bb428ab5c1ff665aa1def6cc14d02a4e19
+Bug-Debian: https://bugs.debian.org/1034190
+
+Technically, a game with no islands is always solved, but it causes a
+null-pointer dereference at startup because there's nowhere to put the
+cursor. Games with one island are always insoluble because the island
+must have at least one bridge and there's nowhere for it to go. So
+the minimum playable game has two islands.
+
+To demonstrate the segfault, try loading this save file:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :7:Bridges
+PARAMS :1:3
+CPARAMS :1:3
+DESC :1:i
+NSTATES :1:1
+STATEPOS:1:1
+---
+ bridges.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/bridges.c b/bridges.c
+index 35cdcca6..510af443 100644
+--- a/bridges.c
++++ b/bridges.c
+@@ -2007,15 +2007,15 @@ static char *new_game_desc(const game_params *params, random_state *rs,
+
+ static const char *validate_desc(const game_params *params, const char *desc)
+ {
+- int i, wh = params->w * params->h;
++ int i, wh = params->w * params->h, nislands = 0;
+
+ for (i = 0; i < wh; i++) {
+ if (*desc >= '1' && *desc <= '9')
+- /* OK */;
++ nislands++;
+ else if (*desc >= 'a' && *desc <= 'z')
+ i += *desc - 'a'; /* plus the i++ */
+ else if (*desc >= 'A' && *desc <= 'G')
+- /* OK */;
++ nislands++;
+ else if (!*desc)
+ return "Game description shorter than expected";
+ else
+@@ -2024,6 +2024,8 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ }
+ if (*desc || i > wh)
+ return "Game description longer than expected";
++ if (nislands < 2)
++ return "Game description has too few islands";
+
+ return NULL;
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0007-Forbid-moves-that-fill-with-the-current-colour-in-Fl.patch sgt-puzzles-20230122.806ae71/debian/patches/0007-Forbid-moves-that-fill-with-the-current-colour-in-Fl.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0007-Forbid-moves-that-fill-with-the-current-colour-in-Fl.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0007-Forbid-moves-that-fill-with-the-current-colour-in-Fl.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,38 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 18:49:47 +0000
+Subject: [PATCH 007/159] Forbid moves that fill with the current colour in
+ Flood
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=eb1ae3f3d041f9ff0c11b04613a695be11bda706
+Bug-Debian: https://bugs.debian.org/1034190
+
+This avoids an assertion failure, "oldcolour != newcolour" in fill(),
+by catching it it execute_move(). As far as I know this couldn't be
+triggered from the UI, but it could be demonstrated with this save
+file:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :5:Flood
+PARAMS :1:3
+CPARAMS :1:3
+DESC :12:231353400,11
+NSTATES :1:3
+STATEPOS:1:3
+MOVE :2:M3
+MOVE :2:M3
+---
+ flood.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/flood.c b/flood.c
+index 1b0fa6e8..08410ba2 100644
+--- a/flood.c
++++ b/flood.c
+@@ -887,6 +887,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ if (move[0] == 'M' &&
+ sscanf(move+1, "%d", &c) == 1 &&
+ c >= 0 &&
++ c != state->grid[FILLY * state->w + FILLX] &&
+ !state->complete) {
+ int *queue = snewn(state->w * state->h, int);
+ ret = dup_game(state);
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0008-Cleanly-reject-ill-formed-solve-moves-in-Flood.patch sgt-puzzles-20230122.806ae71/debian/patches/0008-Cleanly-reject-ill-formed-solve-moves-in-Flood.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0008-Cleanly-reject-ill-formed-solve-moves-in-Flood.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0008-Cleanly-reject-ill-formed-solve-moves-in-Flood.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,43 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 19:06:24 +0000
+Subject: [PATCH 008/159] Cleanly reject ill-formed solve moves in Flood
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=e4112b322e299a461ddc46daee741c73733e186d
+Bug-Debian: https://bugs.debian.org/1034190
+
+A solve move containing characters other than digits and commas would
+cause an assertion failure, "*p == ','", in execute_move(). Such a move
+can't as far as I know be generated in play, but can be read from a
+corrupt save file.
+
+Here's a sample of such a save file:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :5:Flood
+PARAMS :7:3x3c6m5
+CPARAMS :7:3x3c6m5
+DESC :12:403011503,10
+NSTATES :1:2
+STATEPOS:1:2
+SOLVE :2:SA
+---
+ flood.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/flood.c b/flood.c
+index 08410ba2..c015c7e8 100644
+--- a/flood.c
++++ b/flood.c
+@@ -942,7 +942,11 @@ static game_state *execute_move(const game_state *state, const char *move)
+ sol->moves[i] = atoi(p);
+ p += strspn(p, "0123456789");
+ if (*p) {
+- assert(*p == ',');
++ if (*p != ',') {
++ sfree(sol->moves);
++ sfree(sol);
++ return NULL;
++ }
+ p++;
+ }
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0009-Don-t-segfault-on-premature-solve-moves-in-Mines.patch sgt-puzzles-20230122.806ae71/debian/patches/0009-Don-t-segfault-on-premature-solve-moves-in-Mines.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0009-Don-t-segfault-on-premature-solve-moves-in-Mines.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0009-Don-t-segfault-on-premature-solve-moves-in-Mines.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,40 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 19:34:28 +0000
+Subject: [PATCH 009/159] Don't segfault on premature solve moves in Mines
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=28671e76b736aeb860b1f725898c45fe70ae6212
+Bug-Debian: https://bugs.debian.org/1034190
+
+If a save file contained a solve move as the first move, Mines would
+dereference a null pointer trying to look up the (at that point
+undetermined) mine locations. Now execute_move() politely returns
+NULL instead.
+
+This save file demonstrates the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :5:Mines
+PARAMS :5:3x3n0
+CPARAMS :5:3x3n0
+DESC :127:r0,u,7a142789cabddc3fc4dcb7d2baa4a4937b33c9613ea870ac098e217981ad339930af585557d62048ea745d05b01475d9699596b394cc0adeebf0440a02
+UI :2:D0
+TIME :1:0
+NSTATES :1:2
+STATEPOS:1:2
+SOLVE :1:S
+---
+ mines.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mines.c b/mines.c
+index 8af7c206..325c850d 100644
+--- a/mines.c
++++ b/mines.c
+@@ -2637,6 +2637,7 @@ static game_state *execute_move(const game_state *from, const char *move)
+ if (!strcmp(move, "S")) {
+ int yy, xx;
+
++ if (!from->layout->mines) return NULL; /* Game not started. */
+ ret = dup_game(from);
+ if (!ret->dead) {
+ /*
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0010-Limit-number-of-mines-in-Mines-game-description.patch sgt-puzzles-20230122.806ae71/debian/patches/0010-Limit-number-of-mines-in-Mines-game-description.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0010-Limit-number-of-mines-in-Mines-game-description.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0010-Limit-number-of-mines-in-Mines-game-description.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,28 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 23:12:52 +0000
+Subject: [PATCH 010/159] Limit number of mines in Mines game description
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=75e8a1a9cabe7567f6019b1226783b61ba1ac42f
+Bug-Debian: https://bugs.debian.org/1034190
+
+Without this, it's possible to specify a description that has more
+mines than there are places on the board to place them, which
+eventually leads to a division by zero. This can be demonstrated by
+entering a game description of "3:r8,u," and then clicking anywhere on
+the board.
+---
+ mines.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mines.c b/mines.c
+index 325c850d..56748491 100644
+--- a/mines.c
++++ b/mines.c
+@@ -2006,6 +2006,8 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ desc++;
+ if (!*desc || !isdigit((unsigned char)*desc))
+ return "No initial mine count in game description";
++ if (atoi(desc) > wh - 9)
++ return "Too many mines for grid size";
+ while (*desc && isdigit((unsigned char)*desc))
+ desc++; /* skip over mine count */
+ if (*desc != ',')
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0011-Validate-the-number-of-pegs-and-holes-in-a-Pegs-game.patch sgt-puzzles-20230122.806ae71/debian/patches/0011-Validate-the-number-of-pegs-and-holes-in-a-Pegs-game.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0011-Validate-the-number-of-pegs-and-holes-in-a-Pegs-game.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0011-Validate-the-number-of-pegs-and-holes-in-a-Pegs-game.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,44 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 23:45:48 +0000
+Subject: [PATCH 011/159] Validate the number of pegs and holes in a Pegs game
+ ID
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=4359f59dd22770a94e29b2ddd54b533ad1713550
+Bug-Debian: https://bugs.debian.org/1034190
+
+Without this, "1:O" causes an assertion violation, '!"new_ui found
+nowhere for cursor"'. We may as well require two pegs and one hole,
+since that's the minimum for a game in which there are any moves to
+make.
+---
+ pegs.c | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/pegs.c b/pegs.c
+index 54d1a21a..2fa9a2a3 100644
+--- a/pegs.c
++++ b/pegs.c
+@@ -663,7 +663,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
+
+ static const char *validate_desc(const game_params *params, const char *desc)
+ {
+- int len;
++ int len, i, npeg = 0, nhole = 0;
+
+ len = params->w * params->h;
+
+@@ -671,6 +671,15 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ return "Game description is wrong length";
+ if (len != strspn(desc, "PHO"))
+ return "Invalid character in game description";
++ for (i = 0; i < len; i++) {
++ npeg += desc[i] == 'P';
++ nhole += desc[i] == 'H';
++ }
++ /* The minimal soluble game has two pegs and a hole: "3x1:PPH". */
++ if (npeg < 2)
++ return "Too few pegs in game description";
++ if (nhole < 1)
++ return "Too few holes in game description";
+
+ return NULL;
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0017-Mines-forbid-moves-that-flag-or-unflag-an-exposed-sq.patch sgt-puzzles-20230122.806ae71/debian/patches/0017-Mines-forbid-moves-that-flag-or-unflag-an-exposed-sq.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0017-Mines-forbid-moves-that-flag-or-unflag-an-exposed-sq.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0017-Mines-forbid-moves-that-flag-or-unflag-an-exposed-sq.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,30 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Wed, 1 Feb 2023 17:07:12 +0000
+Subject: [PATCH 017/159] Mines: forbid moves that flag or unflag an exposed
+ square
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=17364455186ae61e015d0f0de3f09423f78d0727
+Bug-Debian: https://bugs.debian.org/1034190
+
+interpret_move() couldn't generate them, but execute_move() also needs
+to forbid them to defend against corrupt save files. I don't think this
+actually caused any crashes, but it did cause unexpected "1" squares not
+adjacent to mines.
+---
+ mines.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mines.c b/mines.c
+index 711fc502..d40bb9fb 100644
+--- a/mines.c
++++ b/mines.c
+@@ -2701,7 +2701,9 @@ static game_state *execute_move(const game_state *from, const char *move)
+ while (*move) {
+ if (move[0] == 'F' &&
+ sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+- cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
++ cx >= 0 && cx < from->w && cy >= 0 && cy < from->h &&
++ (ret->grid[cy * from->w + cx] == -1 ||
++ ret->grid[cy * from->w + cx] == -2)) {
+ ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
+ } else if (move[0] == 'O' &&
+ sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0018-Mines-Don-t-check-if-the-player-has-won-if-they-ve-a.patch sgt-puzzles-20230122.806ae71/debian/patches/0018-Mines-Don-t-check-if-the-player-has-won-if-they-ve-a.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0018-Mines-Don-t-check-if-the-player-has-won-if-they-ve-a.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0018-Mines-Don-t-check-if-the-player-has-won-if-they-ve-a.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,44 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Wed, 1 Feb 2023 20:12:29 +0000
+Subject: [PATCH 018/159] Mines: Don't check if the player has won if they've
+ already lost
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=2a9be2b89df3e6a07a1d90a06f8ac00a92d789e5
+Bug-Debian: https://bugs.debian.org/1034190
+
+It can't happen in normal play, but if a save file had a "C" (clear
+around) move that set off a mine, Mines could end up hitting an
+assertion failure, "ncovered >= nmines". This was because it would
+check if the player had won by counting covered squares and mines, and
+of course an uncovered mine is no longer a covered square but is still a
+mine.
+
+Since winning after you're dead isn't possible (at least in Mines), we
+now skip the check entirely if the player has already died.
+
+This save file demonstrates the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :5:Mines
+PARAMS :1:7
+CPARAMS :1:7
+DESC :22:r31,u,0000C000d0000020
+NSTATES :1:2
+STATEPOS:1:1
+MOVE :4:C6,2
+---
+ mines.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mines.c b/mines.c
+index d40bb9fb..16075aaf 100644
+--- a/mines.c
++++ b/mines.c
+@@ -2146,6 +2146,8 @@ static int open_square(game_state *state, int x, int y)
+ break;
+ }
+
++ /* If the player has already lost, don't let them win as well. */
++ if (state->dead) return 0;
+ /*
+ * Finally, scan the grid and see if exactly as many squares
+ * are still covered as there are mines. If so, set the `won'
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0019-Avoid-invalid-moves-when-solving-Tracks.patch sgt-puzzles-20230122.806ae71/debian/patches/0019-Avoid-invalid-moves-when-solving-Tracks.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0019-Avoid-invalid-moves-when-solving-Tracks.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0019-Avoid-invalid-moves-when-solving-Tracks.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,66 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Wed, 1 Feb 2023 21:28:35 +0000
+Subject: [PATCH 019/159] Avoid invalid moves when solving Tracks
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=875f0af21fbced5cbf6cf63b86fe3dc51682c863
+Bug-Debian: https://bugs.debian.org/1034190
+
+The solver, when it decided that an edge or square should be both TRACK
+and NOTRACK, would correctly decide that the puzzle was insoluble, but
+would also mark the edge with both flags in its working copy. This
+could then lead to assertion violations if that working copy of the
+board was used for something else, for instance if it was fed back into
+the solver. This couldn't happen in normal play, since failed solutions
+just cause the solve command to fail, but the diagnostic "H" command
+could trigger it from a save file, causing an assertion failure:
+"state->sflags[y*state->p.w + x] & S_CLUE".
+
+Now when the solver runs into this situation, it marks the puzzle as
+insoluble but doesn't set the invalid flag, so the board remains valid
+and future solve operations are safe.
+
+This save file is the one that demonstrated the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :12:Train Tracks
+PARAMS :5:6x6t0
+CPARAMS :5:6x6t0
+DESC :31:b0t9l,,S0,00,0,0,4,0,0,S0,0,0,0
+NSTATES :1:8
+STATEPOS:1:2
+MOVE :1:H
+MOVE :1:H
+MOVE :1:H
+MOVE :1:H
+MOVE :1:H
+MOVE :1:H
+MOVE :1:H
+---
+ tracks.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/tracks.c b/tracks.c
+index 53754107..6948b42c 100644
+--- a/tracks.c
++++ b/tracks.c
+@@ -925,8 +925,8 @@ static int solve_set_sflag(game_state *state, int x, int y,
+ if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
+ solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
+ state->impossible = true;
+- }
+- state->sflags[i] |= f;
++ } else
++ state->sflags[i] |= f;
+ return 1;
+ }
+
+@@ -943,8 +943,8 @@ static int solve_set_eflag(game_state *state, int x, int y, int d,
+ if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
+ solverdebug(("opposite flag already set there, marking IMPOSSIBLE"));
+ state->impossible = true;
+- }
+- S_E_SET(state, x, y, d, f);
++ } else
++ S_E_SET(state, x, y, d, f);
+ return 1;
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0020-Fix-move-validation-in-Netslide.patch sgt-puzzles-20230122.806ae71/debian/patches/0020-Fix-move-validation-in-Netslide.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0020-Fix-move-validation-in-Netslide.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0020-Fix-move-validation-in-Netslide.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,42 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Wed, 1 Feb 2023 23:00:14 +0000
+Subject: [PATCH 020/159] Fix move validation in Netslide
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=ed0e4c304bed990948541fc0cf87309d75653806
+Bug-Debian: https://bugs.debian.org/1034190
+
+The maximum length of a column move in Netslide is the height of the
+puzzle, and the maximum length of a row move is the width, not the
+other way around.
+
+Moves of absolute length more than 1 can't be generated by
+interpret_move(), but they can come from save files. On non-square
+grids, the incorrect check led to assertion failures: "0 <= tx && tx <
+w" and "0 <= ty && ty < h". This save file demonstrates the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :8:Netslide
+PARAMS :3:4x9
+CPARAMS :3:4x9
+DESC :39:0000000000000h0h0000000000000000000000v
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :4:R0,5
+---
+ netslide.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/netslide.c b/netslide.c
+index 1a01be2e..be83b7cc 100644
+--- a/netslide.c
++++ b/netslide.c
+@@ -1137,8 +1137,8 @@ static game_state *execute_move(const game_state *from, const char *move)
+ if ((move[0] == 'C' || move[0] == 'R') &&
+ sscanf(move+1, "%d,%d", &c, &d) == 2 &&
+ c >= 0 && c < (move[0] == 'C' ? from->width : from->height) &&
+- d <= (move[0] == 'C' ? from->width : from->height) &&
+- d >= -(move[0] == 'C' ? from->width : from->height) && d != 0) {
++ d <= (move[0] == 'C' ? from->height : from->width) &&
++ d >= -(move[0] == 'C' ? from->height : from->width) && d != 0) {
+ col = (move[0] == 'C');
+ } else if (move[0] == 'S' &&
+ strlen(move) == from->width * from->height + 1) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0021-Tighten-validation-of-Tents-game-descriptions.patch sgt-puzzles-20230122.806ae71/debian/patches/0021-Tighten-validation-of-Tents-game-descriptions.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0021-Tighten-validation-of-Tents-game-descriptions.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0021-Tighten-validation-of-Tents-game-descriptions.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,57 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Thu, 2 Feb 2023 21:58:10 +0000
+Subject: [PATCH 021/159] Tighten validation of Tents game descriptions
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=ed682bd5c608156d12ebaa2d84c4ce2e2877c10a
+Bug-Debian: https://bugs.debian.org/1034190
+
+Specifically, TENT and NONTENT markers ('!' and '-') cannot appear as
+the first or last character of a description, because that would
+attempt to place them on squares outside the grid. This was caught by
+assertions in new_game(), as can be demonstrated by feeding these
+descriptions to older versions of Tents: "4:-p,0,0,0,0,0,0,0,0"
+("new_game: Assertion `i >= 0 && i <= w*h' failed.") and
+4:p-,0,0,0,0,0,0,0,0 ("new_game: Assertion `*desc == ','' failed.").
+---
+ tents.c | 22 +++++++++++++++++++---
+ 1 file changed, 19 insertions(+), 3 deletions(-)
+
+diff --git a/tents.c b/tents.c
+index 9e8349c8..f08a0e36 100644
+--- a/tents.c
++++ b/tents.c
+@@ -1191,6 +1191,21 @@ static char *new_game_desc(const game_params *params_in, random_state *rs,
+ return ret;
+ }
+
++/*
++ * Grid description format:
++ *
++ * _ = tree
++ * a = 1 BLANK then TREE
++ * ...
++ * y = 25 BLANKs then TREE
++ * z = 25 BLANKs
++ * ! = set previous square to TENT
++ * - = set previous square to NONTENT
++ *
++ * Last character must be one that would insert a tree as the first
++ * square after the grid.
++ */
++
+ static const char *validate_desc(const game_params *params, const char *desc)
+ {
+ int w = params->w, h = params->h;
+@@ -1204,9 +1219,10 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ area += *desc - 'a' + 2;
+ else if (*desc == 'z')
+ area += 25;
+- else if (*desc == '!' || *desc == '-')
+- /* do nothing */;
+- else
++ else if (*desc == '!' || *desc == '-') {
++ if (area == 0 || area > w * h)
++ return "Tent or non-tent placed off the grid";
++ } else
+ return "Invalid character in grid specification";
+
+ desc++;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0022-Dominosa-require-the-two-halves-of-a-domino-to-be-ad.patch sgt-puzzles-20230122.806ae71/debian/patches/0022-Dominosa-require-the-two-halves-of-a-domino-to-be-ad.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0022-Dominosa-require-the-two-halves-of-a-domino-to-be-ad.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0022-Dominosa-require-the-two-halves-of-a-domino-to-be-ad.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,51 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Thu, 2 Feb 2023 22:26:24 +0000
+Subject: [PATCH 022/159] Dominosa: require the two halves of a domino to be
+ adjacent
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=294a3ac6e703c2820ceb7b28a1a5492b61e9a531
+Bug-Debian: https://bugs.debian.org/1034190
+
+Also that a line indicating no domino be between adjacent squares.
+Without this, execute_move would allow you to place dominos and edges
+between any pair ot squares, and then generate assertion failures
+("execute_move: Assertion `d2 - w >= 0' failed." and "execute_move:
+Assertion `d1 - w >= 0' failed.") when a domino was placed over an
+invalid edge. This example save file demonstrates the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :8:Dominosa
+PARAMS :1:6
+CPARAMS :1:6
+DESC :56:55521461210004364611033535444421636022603153156422620503
+NSTATES :1:3
+STATEPOS:1:3
+MOVE :4:E0,2
+MOVE :4:D0,2
+---
+ dominosa.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/dominosa.c b/dominosa.c
+index e60cb462..61b345e8 100644
+--- a/dominosa.c
++++ b/dominosa.c
+@@ -2896,7 +2896,8 @@ static game_state *execute_move(const game_state *state, const char *move)
+ move++;
+ } else if (move[0] == 'D' &&
+ sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
+- d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2) {
++ d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 &&
++ (d2 - d1 == 1 || d2 - d1 == w)) {
+
+ /*
+ * Toggle domino presence between d1 and d2.
+@@ -2964,7 +2965,8 @@ static game_state *execute_move(const game_state *state, const char *move)
+ } else if (move[0] == 'E' &&
+ sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
+ d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 &&
+- ret->grid[d1] == d1 && ret->grid[d2] == d2) {
++ ret->grid[d1] == d1 && ret->grid[d2] == d2 &&
++ (d2 - d1 == 1 || d2 - d1 == w)) {
+
+ /*
+ * Toggle edge presence between d1 and d2.
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0023-Forbid-lines-off-the-grid-in-Pearl.patch sgt-puzzles-20230122.806ae71/debian/patches/0023-Forbid-lines-off-the-grid-in-Pearl.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0023-Forbid-lines-off-the-grid-in-Pearl.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0023-Forbid-lines-off-the-grid-in-Pearl.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,50 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Thu, 2 Feb 2023 23:09:19 +0000
+Subject: [PATCH 023/159] Forbid lines off the grid in Pearl
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=15f4fa851a5781cf77984a6046405ffa758e7b33
+Bug-Debian: https://bugs.debian.org/1034190
+
+While they couldn't be generated in normal play, execute_move() would
+permit lines and marks across the edge of the grid that would then
+generate assertion failures ("dsf_update_completion: Assertion
+`INGRID(state, bx, by)' failed.").
+
+I've added a check to execute_move() that after updating a square, the
+square doesn't have any lines or marks that leave the grid.
+
+This save file demonstrated the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSZON :1:1
+GAME :5:Pearl
+PARAMS :5:5x6dt
+CPARAMS :5:5x6dt
+DESC :6:eeeeee
+NSTATES :1:2
+STATEPOS:1:1
+MOVE :6:F1,4,2
+---
+ pearl.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/pearl.c b/pearl.c
+index 72f36f7d..f79a6dc2 100644
+--- a/pearl.c
++++ b/pearl.c
+@@ -2278,6 +2278,16 @@ static game_state *execute_move(const game_state *state, const char *move)
+ (ret->marks[y*w + x] & (char)l))
+ goto badmove;
+
++ /*
++ * Similarly, if we've ended up with a line or mark going
++ * off the board, that's not acceptable.
++ */
++ for (l = 1; l <= 8; l <<= 1)
++ if (((ret->lines[y*w + x] & (char)l) ||
++ (ret->marks[y*w + x] & (char)l)) &&
++ !INGRID(state, x+DX(l), y+DY(l)))
++ goto badmove;
++
+ move += n;
+ } else if (strcmp(move, "H") == 0) {
+ pearl_solve(ret->shared->w, ret->shared->h,
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0024-Tolerate-incorrect-solutions-in-Inertia.patch sgt-puzzles-20230122.806ae71/debian/patches/0024-Tolerate-incorrect-solutions-in-Inertia.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0024-Tolerate-incorrect-solutions-in-Inertia.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0024-Tolerate-incorrect-solutions-in-Inertia.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,52 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Fri, 3 Feb 2023 20:52:05 +0000
+Subject: [PATCH 024/159] Tolerate incorrect solutions in Inertia
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=843d4ca17def11671809786f2a5aebd75f230dd9
+Bug-Debian: https://bugs.debian.org/1034190
+
+The "solve" operation in Inertia generates a proposed solution as a
+move string. But if such a move string is loaded from a save file it
+might not actually describe a solution. If that happens then it's
+possible to reach the end of the "solution" without winning, and doing
+so should probably cause a recalculation of the solution rather than
+an assertion failure ("execute_move: Assertion `ret->solnpos <
+ret->soln->len' failed.").
+
+I am a little concerned by the way that normal solve operations end up
+encoded in the save file, but the re-solvings caused by going off
+course don't, but I haven't got a good answer to that.
+
+Here's a save file that demonstrates the assertion failure:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :7:Inertia
+PARAMS :3:8x8
+CPARAMS :3:8x8
+DESC :64:sbgwsmswwgggwggmmbwgwbssbwbsbwbbwsSmwbbsbbmggbmssgmgwbmmwmbmmwsw
+NSTATES :2:3
+STATEPOS:1:1
+MOVE 000:2:S0
+MOVE 000:2:00
+---
+ inertia.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/inertia.c b/inertia.c
+index 2f352d31..b47d58e3 100644
+--- a/inertia.c
++++ b/inertia.c
+@@ -1741,11 +1741,10 @@ static game_state *execute_move(const game_state *state, const char *move)
+ if (ret->soln) {
+ if (ret->dead || ret->gems == 0)
+ discard_solution(ret);
+- else if (ret->soln->list[ret->solnpos] == dir) {
++ else if (ret->soln->list[ret->solnpos] == dir &&
++ ret->solnpos+1 < ret->soln->len)
+ ++ret->solnpos;
+- assert(ret->solnpos < ret->soln->len); /* or gems == 0 */
+- assert(!ret->dead); /* or not a solution */
+- } else {
++ else {
+ const char *error = NULL;
+ char *soln = solve_game(NULL, ret, NULL, &error);
+ if (!error) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0025-Palisade-replace-dfs_dsf-with-a-simple-iteration.patch sgt-puzzles-20230122.806ae71/debian/patches/0025-Palisade-replace-dfs_dsf-with-a-simple-iteration.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0025-Palisade-replace-dfs_dsf-with-a-simple-iteration.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0025-Palisade-replace-dfs_dsf-with-a-simple-iteration.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,111 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Fri, 3 Feb 2023 23:12:38 +0000
+Subject: [PATCH 025/159] Palisade: replace dfs_dsf() with a simple iteration.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=517b14e666b0b71fc0bcd5da1b22cdc90d3434c9
+Bug-Debian: https://bugs.debian.org/1034190
+
+The whole purpose of a dsf is that you can traverse the edges of your
+graph in any order you feel like. So if you want to build the
+connected components of a graph you can just loop over all the edges
+once. There's no need to run a depth-first search.
+
+In fact there were an amazing number of things wrong with this 10-line
+function:
+
+ - As Ben points out in commit 21193eaf9308ace, it didn't bother with
+ bounds checking when searching the grid, instead relying on the
+ never-removed grid boundary to stop the search - which was fragile in
+ the face of other bugs.
+
+ - The recursion uses linear stack, which is much worse than linear
+ heap, since stacks are often much more limited. (And the dsf _also_
+ used linear heap.)
+
+ - The recursion was completely unnecessary.
+
+ - The function used internal knowledge about dsf.c in order to define
+ the value UNVISITED to match what would happen to work.
+
+ - The name 'dfs_dsf' is totally confusing and almost impossible to
+ type!
+---
+ palisade.c | 36 ++++++++++++++++--------------------
+ 1 file changed, 16 insertions(+), 20 deletions(-)
+
+diff --git a/palisade.c b/palisade.c
+index f405efde..6aff0a4d 100644
+--- a/palisade.c
++++ b/palisade.c
+@@ -505,19 +505,20 @@ static bool solver_equivalent_edges(solver_ctx *ctx)
+ return changed;
+ }
+
+-#define UNVISITED 6
+-
+ /* build connected components in `dsf', along the lines of `borders'. */
+-static void dfs_dsf(int i, int w, borderflag *border, int *dsf, bool black)
++static void build_dsf(int w, int h, borderflag *border, int *dsf, bool black)
+ {
+- int dir;
+- for (dir = 0; dir < 4; ++dir) {
+- int ii = i + dx[dir] + w*dy[dir], bdir = BORDER(dir);
+- if (black ? (border[i] & bdir) : !(border[i] & DISABLED(bdir)))
+- continue;
+- if (dsf[ii] != UNVISITED) continue;
+- dsf_merge(dsf, i, ii);
+- dfs_dsf(ii, w, border, dsf, black);
++ int x, y;
++
++ for (y = 0; y < h; y++) {
++ for (x = 0; x < w; x++) {
++ if (x+1 < w && (black ? !(border[y*w+x] & BORDER_R) :
++ (border[y*w+x] & DISABLED(BORDER_R))))
++ dsf_merge(dsf, y*w+x, y*w+(x+1));
++ if (y+1 < h && (black ? !(border[y*w+x] & BORDER_D) :
++ (border[y*w+x] & DISABLED(BORDER_D))))
++ dsf_merge(dsf, y*w+x, (y+1)*w+x);
++ }
+ }
+ }
+
+@@ -528,7 +529,7 @@ static bool is_solved(const game_params *params, clue *clues,
+ int i, x, y;
+ int *dsf = snew_dsf(wh);
+
+- assert (dsf[0] == UNVISITED); /* check: UNVISITED and dsf.c match up */
++ build_dsf(w, h, border, dsf, true);
+
+ /*
+ * A game is solved if:
+@@ -539,7 +540,6 @@ static bool is_solved(const game_params *params, clue *clues,
+ * - the borders also satisfy the clue set
+ */
+ for (i = 0; i < wh; ++i) {
+- if (dsf[i] == UNVISITED) dfs_dsf(i, params->w, border, dsf, true);
+ if (dsf_size(dsf, i) != k) goto error;
+ if (clues[i] == EMPTY) continue;
+ if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error;
+@@ -1179,7 +1179,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
+ float animtime, float flashtime)
+ {
+ int w = state->shared->params.w, h = state->shared->params.h, wh = w*h;
+- int r, c, i, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2;
++ int r, c, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2;
+ int *black_border_dsf = snew_dsf(wh), *yellow_border_dsf = snew_dsf(wh);
+ int k = state->shared->params.k;
+
+@@ -1200,12 +1200,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
+ status_bar(dr, buf);
+ }
+
+- for (i = 0; i < wh; ++i) {
+- if (black_border_dsf[i] == UNVISITED)
+- dfs_dsf(i, w, state->borders, black_border_dsf, true);
+- if (yellow_border_dsf[i] == UNVISITED)
+- dfs_dsf(i, w, state->borders, yellow_border_dsf, false);
+- }
++ build_dsf(w, h, state->borders, black_border_dsf, true);
++ build_dsf(w, h, state->borders, yellow_border_dsf, false);
+
+ for (r = 0; r < h; ++r)
+ for (c = 0; c < w; ++c) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0026-latin_solver_alloc-handle-clashing-numbers-in-input-.patch sgt-puzzles-20230122.806ae71/debian/patches/0026-latin_solver_alloc-handle-clashing-numbers-in-input-.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0026-latin_solver_alloc-handle-clashing-numbers-in-input-.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0026-latin_solver_alloc-handle-clashing-numbers-in-input-.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,223 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 5 Feb 2023 10:29:42 +0000
+Subject: [PATCH 026/159] latin_solver_alloc: handle clashing numbers in input
+ grid.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=5030d87903191d581586ecda2382ad5bcd70f63d
+Bug-Debian: https://bugs.debian.org/1034190
+
+In the setup phase of the centralised latin.c solver, we start by
+going over the input grid containing already-placed clue numbers, and
+calling latin_solver_place to enter each on into the solver's data
+structure. This has the side effect of ruling out each number from the
+rest of the row and column, and _also_ checking by assertion that the
+number being placed is not ruled out.
+
+Those are a bad combination, because it means that if you give an
+obviously inconsistent input grid to latin_solver_alloc (e.g. with two
+identical numbers in a row already), it will fail an assertion. In
+that situation, you want the solver run as a whole to return
+diff_impossible so that the error is reported cleanly.
+
+This assertion failure could be provoked by giving either Towers or
+Group a manually-constructed game description inconsistent in that
+way, and hitting Solve. Worse, it could be provoked during live play
+in Unequal, by filling in a number clashing with a clue and then
+pressing 'h' to get hints.
+---
+ latin.c | 47 +++++++++++++++++++++++++++++-----------------
+ latin.h | 9 ++++++---
+ unequal.c | 30 +++++++++++++++--------------
+ unfinished/group.c | 15 ++++++++-------
+ 4 files changed, 60 insertions(+), 41 deletions(-)
+
+diff --git a/latin.c b/latin.c
+index 39930166..59f306dd 100644
+--- a/latin.c
++++ b/latin.c
+@@ -563,7 +563,7 @@ void latin_solver_free_scratch(struct latin_solver_scratch *scratch)
+ sfree(scratch);
+ }
+
+-void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o)
++bool latin_solver_alloc(struct latin_solver *solver, digit *grid, int o)
+ {
+ int x, y;
+
+@@ -577,14 +577,23 @@ void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o)
+ memset(solver->row, 0, o*o);
+ memset(solver->col, 0, o*o);
+
+- for (x = 0; x < o; x++)
+- for (y = 0; y < o; y++)
+- if (grid[y*o+x])
+- latin_solver_place(solver, x, y, grid[y*o+x]);
+-
+ #ifdef STANDALONE_SOLVER
+ solver->names = NULL;
+ #endif
++
++ for (x = 0; x < o; x++) {
++ for (y = 0; y < o; y++) {
++ int n = grid[y*o+x];
++ if (n) {
++ if (cube(x, y, n))
++ latin_solver_place(solver, x, y, n);
++ else
++ return false; /* puzzle is already inconsistent */
++ }
++ }
++ }
++
++ return true;
+ }
+
+ void latin_solver_free(struct latin_solver *solver)
+@@ -810,15 +819,17 @@ static int latin_solver_recurse
+ } else {
+ newctx = ctx;
+ }
+- latin_solver_alloc(&subsolver, outgrid, o);
+ #ifdef STANDALONE_SOLVER
+ subsolver.names = solver->names;
+ #endif
+- ret = latin_solver_top(&subsolver, diff_recursive,
+- diff_simple, diff_set_0, diff_set_1,
+- diff_forcing, diff_recursive,
+- usersolvers, valid, newctx,
+- ctxnew, ctxfree);
++ if (latin_solver_alloc(&subsolver, outgrid, o))
++ ret = latin_solver_top(&subsolver, diff_recursive,
++ diff_simple, diff_set_0, diff_set_1,
++ diff_forcing, diff_recursive,
++ usersolvers, valid, newctx,
++ ctxnew, ctxfree);
++ else
++ ret = diff_impossible;
+ latin_solver_free(&subsolver);
+ if (ctxnew)
+ ctxfree(newctx);
+@@ -1059,11 +1070,13 @@ int latin_solver(digit *grid, int o, int maxdiff,
+ struct latin_solver solver;
+ int diff;
+
+- latin_solver_alloc(&solver, grid, o);
+- diff = latin_solver_main(&solver, maxdiff,
+- diff_simple, diff_set_0, diff_set_1,
+- diff_forcing, diff_recursive,
+- usersolvers, valid, ctx, ctxnew, ctxfree);
++ if (latin_solver_alloc(&solver, grid, o))
++ diff = latin_solver_main(&solver, maxdiff,
++ diff_simple, diff_set_0, diff_set_1,
++ diff_forcing, diff_recursive,
++ usersolvers, valid, ctx, ctxnew, ctxfree);
++ else
++ diff = diff_impossible;
+ latin_solver_free(&solver);
+ return diff;
+ }
+diff --git a/latin.h b/latin.h
+index bb172ec3..96a04804 100644
+--- a/latin.h
++++ b/latin.h
+@@ -61,10 +61,13 @@ int latin_solver_forcing(struct latin_solver *solver,
+ /* --- Solver allocation --- */
+
+ /* Fills in (and allocates members for) a latin_solver struct.
+- * Will allocate members of snew, but not snew itself
++ * Will allocate members of solver, but not solver itself
+ * (allowing 'struct latin_solver' to be the first element in a larger
+- * struct, for example). */
+-void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o);
++ * struct, for example).
++ *
++ * latin_solver_alloc returns false if the digits already in the grid
++ * could not be legally placed. */
++bool latin_solver_alloc(struct latin_solver *solver, digit *grid, int o);
+ void latin_solver_free(struct latin_solver *solver);
+
+ /* Allocates scratch space (for _set and _forcing) */
+diff --git a/unequal.c b/unequal.c
+index ed03c0be..f2328b68 100644
+--- a/unequal.c
++++ b/unequal.c
+@@ -890,13 +890,14 @@ static int solver_state(game_state *state, int maxdiff)
+ struct latin_solver solver;
+ int diff;
+
+- latin_solver_alloc(&solver, state->nums, state->order);
+-
+- diff = latin_solver_main(&solver, maxdiff,
+- DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
+- DIFF_EXTREME, DIFF_RECURSIVE,
+- unequal_solvers, unequal_valid, ctx,
+- clone_ctx, free_ctx);
++ if (!latin_solver_alloc(&solver, state->nums, state->order))
++ diff = latin_solver_main(&solver, maxdiff,
++ DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
++ DIFF_EXTREME, DIFF_RECURSIVE,
++ unequal_solvers, unequal_valid, ctx,
++ clone_ctx, free_ctx);
++ else
++ diff = DIFF_IMPOSSIBLE;
+
+ memcpy(state->hints, solver.cube, state->order*state->order*state->order);
+
+@@ -2256,13 +2257,14 @@ static int solve(game_params *p, char *desc, int debug)
+ solver_show_working = debug;
+ game_debug(state);
+
+- latin_solver_alloc(&solver, state->nums, state->order);
+-
+- diff = latin_solver_main(&solver, DIFF_RECURSIVE,
+- DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
+- DIFF_EXTREME, DIFF_RECURSIVE,
+- unequal_solvers, unequal_valid, ctx,
+- clone_ctx, free_ctx);
++ if (latin_solver_alloc(&solver, state->nums, state->order))
++ diff = latin_solver_main(&solver, DIFF_RECURSIVE,
++ DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
++ DIFF_EXTREME, DIFF_RECURSIVE,
++ unequal_solvers, unequal_valid, ctx,
++ clone_ctx, free_ctx);
++ else
++ diff = DIFF_IMPOSSIBLE;
+
+ free_ctx(ctx);
+
+diff --git a/unfinished/group.c b/unfinished/group.c
+index e72e3ce3..2eca7c40 100644
+--- a/unfinished/group.c
++++ b/unfinished/group.c
+@@ -580,13 +580,11 @@ static int solver(const game_params *params, digit *grid, int maxdiff)
+ int w = params->w;
+ int ret;
+ struct latin_solver solver;
++
+ #ifdef STANDALONE_SOLVER
+ char *p, text[100], *names[50];
+ int i;
+-#endif
+
+- latin_solver_alloc(&solver, grid, w);
+-#ifdef STANDALONE_SOLVER
+ for (i = 0, p = text; i < w; i++) {
+ names[i] = p;
+ *p++ = TOCHAR(i+1, params->id);
+@@ -595,10 +593,13 @@ static int solver(const game_params *params, digit *grid, int maxdiff)
+ solver.names = names;
+ #endif
+
+- ret = latin_solver_main(&solver, maxdiff,
+- DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
+- DIFF_EXTREME, DIFF_UNREASONABLE,
+- group_solvers, group_valid, NULL, NULL, NULL);
++ if (latin_solver_alloc(&solver, grid, w))
++ ret = latin_solver_main(&solver, maxdiff,
++ DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
++ DIFF_EXTREME, DIFF_UNREASONABLE,
++ group_solvers, group_valid, NULL, NULL, NULL);
++ else
++ ret = diff_impossible;
+
+ latin_solver_free(&solver);
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0027-Pearl-fix-assertion-failure-on-bad-puzzle.patch sgt-puzzles-20230122.806ae71/debian/patches/0027-Pearl-fix-assertion-failure-on-bad-puzzle.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0027-Pearl-fix-assertion-failure-on-bad-puzzle.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0027-Pearl-fix-assertion-failure-on-bad-puzzle.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,87 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 5 Feb 2023 11:19:30 +0000
+Subject: [PATCH 027/159] Pearl: fix assertion failure on bad puzzle.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=05c536e50d0c3114e1de5283eac97ff22bad0fc7
+Bug-Debian: https://bugs.debian.org/1034190
+
+Similarly to the previous commit, if you started Pearl with at least
+some kinds of invalid puzzle (e.g. "6x6:abBfWcWWrBa") and then pressed
+'h' to get hints, you could provoke an assertion failure. But this
+time the assertion wasn't in the solver itself; the solver gave up
+gracefully and didn't crash, but it _did_ leave the links between
+squares in the game_state in an inconsistent state, in that one square
+was marked as linking to its neighbour without the neighbour also
+linking back to it. This caused the /* should have reciprocal link */
+assertion in dsf_update_completion to fail, when that was called from
+check_completion after the solver had finished, to decide whether the
+puzzle was now solved.
+
+In this commit, I arrange that whether or not pearl_solve returns a
+grid layout that's legal by the rules of the _puzzle_, it at least
+returns one that's legal by the rules of the _data representation_, in
+that every link between squares is either bidirectional or absent.
+
+This is a better solution than just removing the assertion, because if
+the inconsistent data were allowed to persist, it would lead to
+further problems in gameplay. For example, if you just remove that
+assertion instead of this fix and press 'h' on the example puzzle id
+above, you'll find that the non-reciprocal links are actually visible,
+in the form of several thick lines that stop at a grid square boundary
+instead of connecting two square-centres. (It looks even sillier if
+you set PEARL_GUI_LOOPY=y.)
+
+That's a situation that can't be created by a normal move, and if you
+try to make normal moves after it (e.g. click one of the weird edges),
+you'll find that both sides of the half-link get toggled, so now it's
+a half-link the other way round. So not only can't you _create_ this
+situation in normal play, you can't get rid of it either!
+
+That assertion in dsf_update_completion was commented out at one
+point, and I put it back in commit c5500926bf7458a saying that if it
+failed I'd like to know about it. And indeed, I'm glad I did, because
+this kind of unfixable wrongness in the resulting game_state was worth
+noticing and getting rid of!
+---
+ pearl.c | 29 +++++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+diff --git a/pearl.c b/pearl.c
+index f79a6dc2..04745f34 100644
+--- a/pearl.c
++++ b/pearl.c
+@@ -855,6 +855,35 @@ static int pearl_solve(int w, int h, char *clues, char *result,
+ if (ret == 1) assert(b < 0xD); /* we should have had a break by now */
+ }
+ }
++
++ /*
++ * Ensure we haven't left the _data structure_ inconsistent,
++ * regardless of the consistency of the _puzzle_. In
++ * particular, we should never have marked one square as
++ * linked to its neighbour if the neighbour is not
++ * reciprocally linked back to the original square.
++ *
++ * This can happen if we get part way through solving an
++ * impossible puzzle and then give up trying to make further
++ * progress. So here we fix it up to avoid confusing the rest
++ * of the game.
++ */
++ for (y = 0; y < h; y++) {
++ for (x = 0; x < w; x++) {
++ for (d = 1; d <= 8; d += d) {
++ int nx = x + DX(d), ny = y + DY(d);
++ int rlink;
++ if (0 <= nx && nx < w && 0 <= ny && ny < w)
++ rlink = result[ny*w+nx] & F(d);
++ else
++ rlink = 0; /* off-board squares don't link back */
++
++ /* If other square doesn't link to us, don't link to it */
++ if (!rlink)
++ result[y*w+x] &= ~d;
++ }
++ }
++ }
+ }
+
+ sfree(dsfsize);
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0028-Pearl-fix-bounds-check-in-previous-commit.patch sgt-puzzles-20230122.806ae71/debian/patches/0028-Pearl-fix-bounds-check-in-previous-commit.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0028-Pearl-fix-bounds-check-in-previous-commit.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0028-Pearl-fix-bounds-check-in-previous-commit.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,24 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 5 Feb 2023 12:05:28 +0000
+Subject: [PATCH 028/159] Pearl: fix bounds check in previous commit.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=9ce0a6d93212ffc0986a2b399fd8e9e983f62904
+Bug-Debian: https://bugs.debian.org/1034190
+
+Ahem. That's what I get for testing the fix on a square puzzle.
+---
+ pearl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pearl.c b/pearl.c
+index 04745f34..f6617a28 100644
+--- a/pearl.c
++++ b/pearl.c
+@@ -873,7 +873,7 @@ static int pearl_solve(int w, int h, char *clues, char *result,
+ for (d = 1; d <= 8; d += d) {
+ int nx = x + DX(d), ny = y + DY(d);
+ int rlink;
+- if (0 <= nx && nx < w && 0 <= ny && ny < w)
++ if (0 <= nx && nx < w && 0 <= ny && ny < h)
+ rlink = result[ny*w+nx] & F(d);
+ else
+ rlink = 0; /* off-board squares don't link back */
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0029-Unequal-Don-t-insist-that-solve-moves-must-actually-.patch sgt-puzzles-20230122.806ae71/debian/patches/0029-Unequal-Don-t-insist-that-solve-moves-must-actually-.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0029-Unequal-Don-t-insist-that-solve-moves-must-actually-.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0029-Unequal-Don-t-insist-that-solve-moves-must-actually-.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,60 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 4 Feb 2023 16:18:27 +0000
+Subject: [PATCH 029/159] Unequal: Don't insist that solve moves must actually
+ solve
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=84ec2a0a77d63450311f7c25b36d4b9f7e3c53e1
+Bug-Debian: https://bugs.debian.org/1034190
+
+A corrupt save file can include an "S" move that doesn't give a valid
+solution. An assertion failure ("execute_move: Assertion `rc > 0'
+failed.") at that point is rude, so now we just don't set the
+"completed" flag in that case. We still set the "cheated" flag, to
+reward (lack of) effort.
+
+Here's a trivial test case:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :7:Unequal
+CPARAMS :1:3
+PARAMS :1:3
+DESC :17:0,0,0,0,0,0,0,0,0
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :10:S222222222
+---
+ unequal.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/unequal.c b/unequal.c
+index f2328b68..0a2bafda 100644
+--- a/unequal.c
++++ b/unequal.c
+@@ -1652,7 +1652,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
+ static game_state *execute_move(const game_state *state, const char *move)
+ {
+ game_state *ret = NULL;
+- int x, y, n, i, rc;
++ int x, y, n, i;
+
+ debug(("execute_move: %s", move));
+
+@@ -1677,7 +1677,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ const char *p;
+
+ ret = dup_game(state);
+- ret->completed = ret->cheated = true;
++ ret->cheated = true;
+
+ p = move+1;
+ for (i = 0; i < state->order*state->order; i++) {
+@@ -1688,8 +1688,8 @@ static game_state *execute_move(const game_state *state, const char *move)
+ p++;
+ }
+ if (*p) goto badmove;
+- rc = check_complete(ret->nums, ret, true);
+- assert(rc > 0);
++ if (!ret->completed && check_complete(ret->nums, ret, true) > 0)
++ ret->completed = true;
+ return ret;
+ } else if (move[0] == 'M') {
+ ret = dup_game(state);
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0030-Range-Don-t-fail-an-assertion-on-an-all-black-board.patch sgt-puzzles-20230122.806ae71/debian/patches/0030-Range-Don-t-fail-an-assertion-on-an-all-black-board.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0030-Range-Don-t-fail-an-assertion-on-an-all-black-board.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0030-Range-Don-t-fail-an-assertion-on-an-all-black-board.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,29 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 4 Feb 2023 16:50:55 +0000
+Subject: [PATCH 030/159] Range: Don't fail an assertion on an all-black board
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=ae73ad76ef95f0e40868436cb750126322051dd0
+Bug-Debian: https://bugs.debian.org/1034190
+
+If there are no white squares, then Range's check that all the white
+squares form a connected component goes wrong. Skip the check in that
+case to avoid an assretion violation ("edsf_canonify: Assertion `index
+>= 0' failed."). This can be demonstrated by starting a game with no
+clues (e.g. "range 3:i") and then filling in every square.
+---
+ range.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/range.c b/range.c
+index bc70c05b..c360b753 100644
+--- a/range.c
++++ b/range.c
+@@ -1494,7 +1494,8 @@ static bool find_errors(const game_state *state, bool *report)
+ if (state->grid[r*w+c] != BLACK &&
+ state->grid[r*w+(c+1)] != BLACK)
+ dsf_merge(dsf, r*w+c, r*w+(c+1));
+- if (nblack + dsf_size(dsf, any_white_cell) < n) {
++ if (any_white_cell != -1 &&
++ nblack + dsf_size(dsf, any_white_cell) < n) {
+ int biggest, canonical;
+
+ if (!report) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0031-Limit-width-and-height-to-SHRT_MAX-in-Mines.patch sgt-puzzles-20230122.806ae71/debian/patches/0031-Limit-width-and-height-to-SHRT_MAX-in-Mines.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0031-Limit-width-and-height-to-SHRT_MAX-in-Mines.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0031-Limit-width-and-height-to-SHRT_MAX-in-Mines.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,41 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 28 Jan 2023 22:27:21 +0000
+Subject: [PATCH 031/159] Limit width and height to SHRT_MAX in Mines
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=c0e08f308792b15425e10ad494263d77a45ad92d
+Bug-Debian: https://bugs.debian.org/1034190
+
+Mines' "struct set" stores co-ordinates within the grid in a pair of
+shorts, which leads to very bad behaviour (including heap-based buffer
+overruns) if the grid is bigger than SHRT_MAX in either dimension. So
+now we don't allow that.
+
+The overrun can be demonstrated by loading this save file, though the
+precise crash is quite variable. In particular, you seem to get
+better crashes if the file doesn't have a trailing newline.
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+PARAMS :5:06000
+CPARAMS :7:6x60000
+NSTATES :1:3
+STATEPOS:1:2
+MOVE :5:C0,00
+GAME :5:Mines
+DESC :22:r8,u,00000000000000000
+MOVE ::
+---
+ mines.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mines.c b/mines.c
+index 16075aaf..c6660010 100644
+--- a/mines.c
++++ b/mines.c
+@@ -263,6 +263,8 @@ static const char *validate_params(const game_params *params, bool full)
+ return "Width and height must both be greater than two";
+ if (params->w < 1 || params->h < 1)
+ return "Width and height must both be at least one";
++ if (params->w > SHRT_MAX || params->h > SHRT_MAX)
++ return "Neither width nor height may be unreasonably large";
+ if (params->w > INT_MAX / params->h)
+ return "Width times height must not be unreasonably large";
+ if (params->n < 0)
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0032-Mines-Add-assertions-to-range-check-conversions-to-s.patch sgt-puzzles-20230122.806ae71/debian/patches/0032-Mines-Add-assertions-to-range-check-conversions-to-s.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0032-Mines-Add-assertions-to-range-check-conversions-to-s.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0032-Mines-Add-assertions-to-range-check-conversions-to-s.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,37 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Tue, 31 Jan 2023 21:08:05 +0000
+Subject: [PATCH 032/159] Mines: Add assertions to range-check conversions to
+ short
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=49841bd0fc04490d94cf32c0e6f9d3f4ffabe098
+Bug-Debian: https://bugs.debian.org/1034190
+
+I think these should be adequately guarded by the new restrictions on
+grid size, but I'd prefer to be sure.
+---
+ mines.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mines.c b/mines.c
+index c6660010..04364e5a 100644
+--- a/mines.c
++++ b/mines.c
+@@ -435,7 +435,9 @@ static void ss_add(struct setstore *ss, int x, int y, int mask, int mines)
+ * Create a set structure and add it to the tree.
+ */
+ s = snew(struct set);
++ assert(SHRT_MIN <= x && x <= SHRT_MAX);
+ s->x = x;
++ assert(SHRT_MIN <= y && y <= SHRT_MAX);
+ s->y = y;
+ s->mask = mask;
+ s->mines = mines;
+@@ -506,7 +508,9 @@ static struct set **ss_overlap(struct setstore *ss, int x, int y, int mask)
+ /*
+ * Find the first set with these top left coordinates.
+ */
++ assert(SHRT_MIN <= xx && xx <= SHRT_MAX);
+ stmp.x = xx;
++ assert(SHRT_MIN <= yy && yy <= SHRT_MAX);
+ stmp.y = yy;
+ stmp.mask = 0;
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0033-Unequal-fix-sense-error-in-latin_solver_alloc-fix.patch sgt-puzzles-20230122.806ae71/debian/patches/0033-Unequal-fix-sense-error-in-latin_solver_alloc-fix.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0033-Unequal-fix-sense-error-in-latin_solver_alloc-fix.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0033-Unequal-fix-sense-error-in-latin_solver_alloc-fix.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,30 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Wed, 8 Feb 2023 18:22:23 +0000
+Subject: [PATCH 033/159] Unequal: fix sense error in latin_solver_alloc fix.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=bd5c0a37a019c540eda05f8291cad90ffd598134
+Bug-Debian: https://bugs.debian.org/1034190
+
+In commit 5030d87903191d5 I gave latin_solver_alloc a return value,
+and introduced a check of that value at every call site. One of the
+checks was backwards, with the effect that Unequal game generation now
+more or less always fails an assertion. For example:
+
+$ unequal --generate 1 4#12345
+unequal: unequal.c:1072: gg_best_clue: Assertion `best != -1' failed.
+---
+ unequal.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/unequal.c b/unequal.c
+index 0a2bafda..52041bbd 100644
+--- a/unequal.c
++++ b/unequal.c
+@@ -890,7 +890,7 @@ static int solver_state(game_state *state, int maxdiff)
+ struct latin_solver solver;
+ int diff;
+
+- if (!latin_solver_alloc(&solver, state->nums, state->order))
++ if (latin_solver_alloc(&solver, state->nums, state->order))
+ diff = latin_solver_main(&solver, maxdiff,
+ DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
+ DIFF_EXTREME, DIFF_RECURSIVE,
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0034-Forbid-impossible-moves-in-Bridges.patch sgt-puzzles-20230122.806ae71/debian/patches/0034-Forbid-impossible-moves-in-Bridges.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0034-Forbid-impossible-moves-in-Bridges.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0034-Forbid-impossible-moves-in-Bridges.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,48 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Fri, 10 Feb 2023 17:09:18 +0000
+Subject: [PATCH 034/159] Forbid impossible moves in Bridges
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=bf9abb2a127a4a81babe50ecb419527f8aeffe28
+Bug-Debian: https://bugs.debian.org/1034190
+
+Specifically, a bridge or a non-bridge must connect two islands that
+differ in precisely one co-ordinate. Without this, a save file that
+tries to connect or disconnect two non-orthogonal islands will cause
+"island_join: Assertion `!"island_join: islands not orthogonal."'
+failed."
+
+Here's a save file demonstrating the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :7:Bridges
+PARAMS :13:3x3i30e10m2d0
+CPARAMS :13:3x3i30e10m2d0
+DESC :6:b1c1a2
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :10:L0,2,2,0,1
+---
+ bridges.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/bridges.c b/bridges.c
+index db4a66e6..32263348 100644
+--- a/bridges.c
++++ b/bridges.c
+@@ -2571,6 +2571,8 @@ static game_state *execute_move(const game_state *state, const char *move)
+ goto badmove;
+ if (!INGRID(ret, x1, y1) || !INGRID(ret, x2, y2))
+ goto badmove;
++ /* Precisely one co-ordinate must differ between islands. */
++ if ((x1 != x2) + (y1 != y2) != 1) goto badmove;
+ is1 = INDEX(ret, gridi, x1, y1);
+ is2 = INDEX(ret, gridi, x2, y2);
+ if (!is1 || !is2) goto badmove;
+@@ -2582,6 +2584,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ goto badmove;
+ if (!INGRID(ret, x1, y1) || !INGRID(ret, x2, y2))
+ goto badmove;
++ if ((x1 != x2) + (y1 != y2) != 1) goto badmove;
+ is1 = INDEX(ret, gridi, x1, y1);
+ is2 = INDEX(ret, gridi, x2, y2);
+ if (!is1 || !is2) goto badmove;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0035-Forbid-game-descriptions-with-joined-islands-in-Brid.patch sgt-puzzles-20230122.806ae71/debian/patches/0035-Forbid-game-descriptions-with-joined-islands-in-Brid.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0035-Forbid-game-descriptions-with-joined-islands-in-Brid.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0035-Forbid-game-descriptions-with-joined-islands-in-Brid.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,64 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Fri, 10 Feb 2023 18:28:36 +0000
+Subject: [PATCH 035/159] Forbid game descriptions with joined islands in
+ Bridges
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=ad2fb760fc881d1ceb1ac1151bf60b85deb16c71
+Bug-Debian: https://bugs.debian.org/1034190
+
+A game description with islands in adjacent grid squares, like
+"3x3:11g", shouldn't be allowed. If it is, then bridges between the
+islands are invisible and clicking one of them causes an assertion
+failure: "Assertion `is_loop->adj.points[j].off > 1' failed."
+
+The code to check this is really rather complex, but I think the
+complexity is mostly necessary.
+---
+ bridges.c | 27 ++++++++++++++++++++-------
+ 1 file changed, 20 insertions(+), 7 deletions(-)
+
+diff --git a/bridges.c b/bridges.c
+index 32263348..c95b38c6 100644
+--- a/bridges.c
++++ b/bridges.c
+@@ -2007,21 +2007,34 @@ static char *new_game_desc(const game_params *params, random_state *rs,
+
+ static const char *validate_desc(const game_params *params, const char *desc)
+ {
+- int i, wh = params->w * params->h, nislands = 0;
++ int i, j, wh = params->w * params->h, nislands = 0;
++ bool *last_row = snewn(params->w, bool);
+
++ memset(last_row, 0, params->w * sizeof(bool));
+ for (i = 0; i < wh; i++) {
+- if (*desc >= '1' && *desc <= '9')
++ if ((*desc >= '1' && *desc <= '9') || (*desc >= 'A' && *desc <= 'G')) {
+ nislands++;
+- else if (*desc >= 'a' && *desc <= 'z')
++ /* Look for other islands to the left and above. */
++ if ((i % params->w > 0 && last_row[i % params->w - 1]) ||
++ last_row[i % params->w]) {
++ sfree(last_row);
++ return "Game description contains joined islands";
++ }
++ last_row[i % params->w] = true;
++ } else if (*desc >= 'a' && *desc <= 'z') {
++ for (j = 0; j < *desc - 'a' + 1; j++)
++ last_row[(i + j) % params->w] = false;
+ i += *desc - 'a'; /* plus the i++ */
+- else if (*desc >= 'A' && *desc <= 'G')
+- nislands++;
+- else if (!*desc)
++ } else if (!*desc) {
++ sfree(last_row);
+ return "Game description shorter than expected";
+- else
++ } else {
++ sfree(last_row);
+ return "Game description contains unexpected character";
++ }
+ desc++;
+ }
++ sfree(last_row);
+ if (*desc || i > wh)
+ return "Game description longer than expected";
+ if (nislands < 2)
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0037-Check-state-is-valid-at-the-end-of-a-move-in-Pearl.patch sgt-puzzles-20230122.806ae71/debian/patches/0037-Check-state-is-valid-at-the-end-of-a-move-in-Pearl.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0037-Check-state-is-valid-at-the-end-of-a-move-in-Pearl.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0037-Check-state-is-valid-at-the-end-of-a-move-in-Pearl.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,131 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 11 Feb 2023 21:22:49 +0000
+Subject: [PATCH 037/159] Check state is valid at the end of a move in Pearl
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=c0b2f0fc98e87392dcb4dd8faf3076786fc49367
+Bug-Debian: https://bugs.debian.org/1034190
+
+A Pearl move string contains a sequence of sub-moves, each of which
+can affect the state of the connection between the centre of a square
+and one of its edges. interpret_move() generates these in pairs so
+that the two halves of a connection between the centres of adjacent
+squares stay in the same state.
+
+If, however, a save file contains mismatched half-moves,
+execute_move() should ideally return NULL rather than causing an
+assertion failure. This has to be checked at the end of the whole
+move string, so I've arranged for check_completion() to return a
+boolean indicating whether the current state (and hence the move
+preceding it) is valid. It now returns 'false' when a connection
+stops at a square boundary or when it goes off the board. These
+conditions used to be assertion failures, and now they just cause the
+move to be rejected.
+
+This supersedes the check for off-board connections added in 15f4fa8,
+since now check_completion() can check for off-board links for the
+whole board at once.
+
+This save file trivially demonstrates the problem, causing
+"dsf_update_completion: Assertion `state->lines[bc] & F(dir)' failed"
+without this fix:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :5:Pearl
+PARAMS :5:6x6t0
+CPARAMS :5:6x6t0
+DESC :17:BbBfWceBbWaBWWgWB
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :6:R1,0,0
+---
+ pearl.c | 37 ++++++++++++++++++-------------------
+ 1 file changed, 18 insertions(+), 19 deletions(-)
+
+diff --git a/pearl.c b/pearl.c
+index f6617a28..29dd9cd7 100644
+--- a/pearl.c
++++ b/pearl.c
+@@ -1525,25 +1525,30 @@ static char nbits[16] = { 0, 1, 1, 2,
+
+ #define ERROR_CLUE 16
+
+-static void dsf_update_completion(game_state *state, int ax, int ay, char dir,
++/* Returns false if the state is invalid. */
++static bool dsf_update_completion(game_state *state, int ax, int ay, char dir,
+ int *dsf)
+ {
+ int w = state->shared->w /*, h = state->shared->h */;
+ int ac = ay*w+ax, bx, by, bc;
+
+- if (!(state->lines[ac] & dir)) return; /* no link */
++ if (!(state->lines[ac] & dir)) return true; /* no link */
+ bx = ax + DX(dir); by = ay + DY(dir);
+
+- assert(INGRID(state, bx, by)); /* should not have a link off grid */
++ if (!INGRID(state, bx, by))
++ return false; /* should not have a link off grid */
+
+ bc = by*w+bx;
+- assert(state->lines[bc] & F(dir)); /* should have reciprocal link */
+- if (!(state->lines[bc] & F(dir))) return;
++ if (!(state->lines[bc] & F(dir)))
++ return false; /* should have reciprocal link */
++ if (!(state->lines[bc] & F(dir))) return true;
+
+ dsf_merge(dsf, ac, bc);
++ return true;
+ }
+
+-static void check_completion(game_state *state, bool mark)
++/* Returns false if the state is invalid. */
++static bool check_completion(game_state *state, bool mark)
+ {
+ int w = state->shared->w, h = state->shared->h, x, y, i, d;
+ bool had_error = false;
+@@ -1571,8 +1576,11 @@ static void check_completion(game_state *state, bool mark)
+ /* Build the dsf. */
+ for (x = 0; x < w; x++) {
+ for (y = 0; y < h; y++) {
+- dsf_update_completion(state, x, y, R, dsf);
+- dsf_update_completion(state, x, y, D, dsf);
++ if (!dsf_update_completion(state, x, y, R, dsf) ||
++ !dsf_update_completion(state, x, y, D, dsf)) {
++ sfree(dsf);
++ return false;
++ }
+ }
+ }
+
+@@ -1727,6 +1735,7 @@ static void check_completion(game_state *state, bool mark)
+ if (!had_error)
+ state->completed = true;
+ }
++ return true;
+ }
+
+ /* completion check:
+@@ -2307,16 +2316,6 @@ static game_state *execute_move(const game_state *state, const char *move)
+ (ret->marks[y*w + x] & (char)l))
+ goto badmove;
+
+- /*
+- * Similarly, if we've ended up with a line or mark going
+- * off the board, that's not acceptable.
+- */
+- for (l = 1; l <= 8; l <<= 1)
+- if (((ret->lines[y*w + x] & (char)l) ||
+- (ret->marks[y*w + x] & (char)l)) &&
+- !INGRID(state, x+DX(l), y+DY(l)))
+- goto badmove;
+-
+ move += n;
+ } else if (strcmp(move, "H") == 0) {
+ pearl_solve(ret->shared->w, ret->shared->h,
+@@ -2333,7 +2332,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ goto badmove;
+ }
+
+- check_completion(ret, true);
++ if (!check_completion(ret, true)) goto badmove;
+
+ return ret;
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0038-Cleanly-reject-more-ill-formed-solve-moves-in-Flood.patch sgt-puzzles-20230122.806ae71/debian/patches/0038-Cleanly-reject-more-ill-formed-solve-moves-in-Flood.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0038-Cleanly-reject-more-ill-formed-solve-moves-in-Flood.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0038-Cleanly-reject-more-ill-formed-solve-moves-in-Flood.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,47 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 11 Feb 2023 22:00:49 +0000
+Subject: [PATCH 038/159] Cleanly reject more ill-formed solve moves in Flood
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=896a73bd7ff8cbde44e97d89cef57346478f0072
+Bug-Debian: https://bugs.debian.org/1034190
+
+The fix in e4112b3 was incomplete: there was another assertion that could be failed by a save file with an ill-formed solve move. That now gets rejected properly. Here's an example save file to demonstrate the problem:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :5:Flood
+PARAMS :7:6x6c6m0
+CPARAMS :7:6x6c6m0
+DESC :39:000000000000000000000000000000000000,00
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :1:S
+---
+ flood.c | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/flood.c b/flood.c
+index 8f4df003..f1ac5e1c 100644
+--- a/flood.c
++++ b/flood.c
+@@ -938,15 +938,16 @@ static game_state *execute_move(const game_state *state, const char *move)
+
+ sol->moves = snewn(sol->nmoves, char);
+ for (i = 0, p = move; i < sol->nmoves; i++) {
+- assert(*p);
++ if (!*p) {
++ badsolve:
++ sfree(sol->moves);
++ sfree(sol);
++ return NULL;
++ };
+ sol->moves[i] = atoi(p);
+ p += strspn(p, "0123456789");
+ if (*p) {
+- if (*p != ',') {
+- sfree(sol->moves);
+- sfree(sol);
+- return NULL;
+- }
++ if (*p != ',') goto badsolve;
+ p++;
+ }
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0039-Don-t-allow-moves-that-change-the-constraints-in-Une.patch sgt-puzzles-20230122.806ae71/debian/patches/0039-Don-t-allow-moves-that-change-the-constraints-in-Une.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0039-Don-t-allow-moves-that-change-the-constraints-in-Une.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0039-Don-t-allow-moves-that-change-the-constraints-in-Une.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,53 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 11 Feb 2023 22:49:36 +0000
+Subject: [PATCH 039/159] Don't allow moves that change the constraints in
+ Unequal
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=97b03cc67a31c1d0869a21c50b9ca31f78775ff9
+Bug-Debian: https://bugs.debian.org/1034190
+
+Unequal has a flags word per cell. Some of those flags are fixed,
+like the locations of the ">" signs, but others indicate errors and
+are used to allow the player to mark clues as "spent". Move strings
+beginning with "F" allow the user to change the "spent" flags, but
+they shouldn't allow the user to change any other flags, especially
+those marking the constraints.
+
+Without this fix, the following save file gives a "solver_nminmax:
+Assertion `x >= 0 && y >= 0 && x < o && y < o' failed" after it adds a
+clue that points off the board:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+GAME :7:Unequal
+PARAMS :3:3e0
+CPARAMS :3:3e0
+DESC :17:0,0,0,0,0,0,0,0,0
+NSTATES :2:3
+STATEPOS:1:3
+MOVE :6:F2,0,4
+MOVE :1:H
+---
+ unequal.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/unequal.c b/unequal.c
+index 52041bbd..e0e08a12 100644
+--- a/unequal.c
++++ b/unequal.c
+@@ -84,6 +84,7 @@ struct game_params {
+ #define ADJ_TO_SPENT(x) ((x) << 9)
+
+ #define F_ERROR_MASK (F_ERROR|F_ERROR_UP|F_ERROR_RIGHT|F_ERROR_DOWN|F_ERROR_LEFT)
++#define F_SPENT_MASK (F_SPENT_UP|F_SPENT_RIGHT|F_SPENT_DOWN|F_SPENT_LEFT)
+
+ struct game_state {
+ int order;
+@@ -1706,7 +1707,8 @@ static game_state *execute_move(const game_state *state, const char *move)
+ check_complete(ret->nums, ret, true);
+ return ret;
+ } else if (move[0] == 'F' && sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+- x >= 0 && x < state->order && y >= 0 && y < state->order) {
++ x >= 0 && x < state->order && y >= 0 && y < state->order &&
++ (n & ~F_SPENT_MASK) == 0) {
+ ret = dup_game(state);
+ GRID(ret, flags, x, y) ^= n;
+ return ret;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0041-Fix-memory-leaks-in-Keen-s-validate_desc.patch sgt-puzzles-20230122.806ae71/debian/patches/0041-Fix-memory-leaks-in-Keen-s-validate_desc.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0041-Fix-memory-leaks-in-Keen-s-validate_desc.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0041-Fix-memory-leaks-in-Keen-s-validate_desc.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,52 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 09:31:03 +0000
+Subject: [PATCH 041/159] Fix memory leaks in Keen's validate_desc()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=bb31efdbc91495a4385ca0afffa4c8bb8f564d7b
+Bug-Debian: https://bugs.debian.org/1034190
+
+Keen uses a DSF to validate its game descriptions and almost always
+failed to free it, even when the validation succeeded.
+---
+ keen.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/keen.c b/keen.c
+index be06013d..98ddc0c4 100644
+--- a/keen.c
++++ b/keen.c
+@@ -1310,8 +1310,10 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ return ret;
+ }
+
+- if (*p != ',')
++ if (*p != ',') {
++ sfree(dsf);
+ return "Expected ',' after block structure description";
++ }
+ p++;
+
+ /*
+@@ -1323,17 +1325,22 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ if (*p == 'a' || *p == 'm') {
+ /* these clues need no validation */
+ } else if (*p == 'd' || *p == 's') {
+- if (dsf_size(dsf, i) != 2)
++ if (dsf_size(dsf, i) != 2) {
++ sfree(dsf);
+ return "Subtraction and division blocks must have area 2";
++ }
+ } else if (!*p) {
++ sfree(dsf);
+ return "Too few clues for block structure";
+ } else {
++ sfree(dsf);
+ return "Unrecognised clue type";
+ }
+ p++;
+ while (*p && isdigit((unsigned char)*p)) p++;
+ }
+ }
++ sfree(dsf);
+ if (*p)
+ return "Too many clues for block structure";
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0042-Remember-to-free-the-actual_board-array-in-Mosaic.patch sgt-puzzles-20230122.806ae71/debian/patches/0042-Remember-to-free-the-actual_board-array-in-Mosaic.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0042-Remember-to-free-the-actual_board-array-in-Mosaic.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0042-Remember-to-free-the-actual_board-array-in-Mosaic.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,22 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 09:36:27 +0000
+Subject: [PATCH 042/159] Remember to free the actual_board array in Mosaic
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=0f20b7226957a2fceff37a67b0d9874c7db3a7fc
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ mosaic.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mosaic.c b/mosaic.c
+index f3a32167..842c211c 100644
+--- a/mosaic.c
++++ b/mosaic.c
+@@ -925,6 +925,7 @@ static void free_game(game_state *state)
+ sfree(state->cells_contents);
+ state->cells_contents = NULL;
+ if (state->board->references <= 1) {
++ sfree(state->board->actual_board);
+ sfree(state->board);
+ state->board = NULL;
+ } else {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0043-Don-t-leak-grids-in-Loopy-s-validate_desc.patch sgt-puzzles-20230122.806ae71/debian/patches/0043-Don-t-leak-grids-in-Loopy-s-validate_desc.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0043-Don-t-leak-grids-in-Loopy-s-validate_desc.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0043-Don-t-leak-grids-in-Loopy-s-validate_desc.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,35 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 09:45:08 +0000
+Subject: [PATCH 043/159] Don't leak grids in Loopy's validate_desc()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=11b631ea870355306c4b1d03458bb3cea8f29188
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ loopy.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/loopy.c b/loopy.c
+index 960fafad..1e041f92 100644
+--- a/loopy.c
++++ b/loopy.c
+@@ -791,13 +791,18 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ count += *desc - 'a' + 1;
+ continue;
+ }
++ grid_free(g);
+ return "Unknown character in description";
+ }
+
+- if (count < g->num_faces)
++ if (count < g->num_faces) {
++ grid_free(g);
+ return "Description too short for board size";
+- if (count > g->num_faces)
++ }
++ if (count > g->num_faces) {
++ grid_free(g);
+ return "Description too long for board size";
++ }
+
+ grid_free(g);
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0044-Remember-to-free-the-to_draw-member-from-Net-s-draws.patch sgt-puzzles-20230122.806ae71/debian/patches/0044-Remember-to-free-the-to_draw-member-from-Net-s-draws.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0044-Remember-to-free-the-to_draw-member-from-Net-s-draws.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0044-Remember-to-free-the-to_draw-member-from-Net-s-draws.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,23 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 09:48:16 +0000
+Subject: [PATCH 044/159] Remember to free the to_draw member from Net's
+ drawstate
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=493bf16ddbe2185664d6c3053f7891a9f232c75c
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ net.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net.c b/net.c
+index cff0c9c2..9ac22707 100644
+--- a/net.c
++++ b/net.c
+@@ -2464,6 +2464,7 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+ {
+ sfree(ds->visible);
++ sfree(ds->to_draw);
+ sfree(ds);
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0045-Undead-check-the-return-value-of-sscanf-in-execute_m.patch sgt-puzzles-20230122.806ae71/debian/patches/0045-Undead-check-the-return-value-of-sscanf-in-execute_m.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0045-Undead-check-the-return-value-of-sscanf-in-execute_m.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0045-Undead-check-the-return-value-of-sscanf-in-execute_m.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,31 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 10:04:47 +0000
+Subject: [PATCH 045/159] Undead: check the return value of sscanf() in
+ execute_move()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=0a7c531e8f4c1970662f7c30aea006e65d5ff010
+Bug-Debian: https://bugs.debian.org/1034190
+
+sscanf() assigns its output in order, so if a conversion specifier fails
+to match, a later "%n" specifier will also not get its result assigned.
+In Undead's execute_move(), this led to the result of "%n" being used
+without being initialised. That could cause it to try to parse
+arbitrary memory as part of the move string, which shouldn't be a
+security problem (since execute_move() handles untrusted input anyway),
+but could lead to a crash and certainly wasn't helpful.
+---
+ undead.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/undead.c b/undead.c
+index 40339ae0..6feca640 100644
+--- a/undead.c
++++ b/undead.c
+@@ -2083,7 +2083,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ } else if (c == 'G' || c == 'V' || c == 'Z' || c == 'E' ||
+ c == 'g' || c == 'v' || c == 'z') {
+ move++;
+- sscanf(move, "%d%n", &x, &n);
++ if (sscanf(move, "%d%n", &x, &n) != 1) goto badmove;
+ if (x < 0 || x >= ret->common->num_total) goto badmove;
+ if (c == 'G') ret->guess[x] = 1;
+ if (c == 'V') ret->guess[x] = 2;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0046-Don-t-leak-duplicate-edges-in-Untangle.patch sgt-puzzles-20230122.806ae71/debian/patches/0046-Don-t-leak-duplicate-edges-in-Untangle.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0046-Don-t-leak-duplicate-edges-in-Untangle.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0046-Don-t-leak-duplicate-edges-in-Untangle.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,29 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 11:08:14 +0000
+Subject: [PATCH 046/159] Don't leak duplicate edges in Untangle
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=19401e95e0a75577103e9c1a877611234a0d8ab5
+Bug-Debian: https://bugs.debian.org/1034190
+
+Untangle game descriptions are allowed to contain duplicate edges, and
+add234() can handle deduping them. However, when add234() reports that
+your newly-allocated edge is a duplicate, it's important to free it
+again.
+---
+ untangle.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/untangle.c b/untangle.c
+index 1dc4fb30..34924ae6 100644
+--- a/untangle.c
++++ b/untangle.c
+@@ -420,7 +420,9 @@ static void addedge(tree234 *edges, int a, int b)
+ e->a = min(a, b);
+ e->b = max(a, b);
+
+- add234(edges, e);
++ if (add234(edges, e) != e)
++ /* Duplicate edge. */
++ sfree(e);
+ }
+
+ static bool isedge(tree234 *edges, int a, int b)
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0047-Remember-to-free-the-numcolours-array-from-Pattern-s.patch sgt-puzzles-20230122.806ae71/debian/patches/0047-Remember-to-free-the-numcolours-array-from-Pattern-s.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0047-Remember-to-free-the-numcolours-array-from-Pattern-s.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0047-Remember-to-free-the-numcolours-array-from-Pattern-s.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,23 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 11:13:03 +0000
+Subject: [PATCH 047/159] Remember to free the numcolours array from Pattern's
+ drawstate
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=1aa67e7a75ac21d15780d6aaab65a2c0f6f65198
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ pattern.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/pattern.c b/pattern.c
+index 68862df8..1342cb64 100644
+--- a/pattern.c
++++ b/pattern.c
+@@ -1738,6 +1738,7 @@ static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+ static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+ {
+ sfree(ds->visible);
++ sfree(ds->numcolours);
+ sfree(ds->strbuf);
+ sfree(ds);
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0048-Free-new-game_state-properly-in-Mosaic-s-execute_mov.patch sgt-puzzles-20230122.806ae71/debian/patches/0048-Free-new-game_state-properly-in-Mosaic-s-execute_mov.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0048-Free-new-game_state-properly-in-Mosaic-s-execute_mov.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0048-Free-new-game_state-properly-in-Mosaic-s-execute_mov.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,48 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 11:21:11 +0000
+Subject: [PATCH 048/159] Free new game_state properly in Mosaic's
+ execute_move()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=d577aaecab09506988a657fa257c4d0ab85d0cd6
+Bug-Debian: https://bugs.debian.org/1034190
+
+Using sfree() rather than free_game() in the error paths meant that
+various arrays referenced from the game_state weren't properly freed.
+Also one error path didn't free the game_state at all.
+---
+ mosaic.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/mosaic.c b/mosaic.c
+index 842c211c..74a06d0f 100644
+--- a/mosaic.c
++++ b/mosaic.c
+@@ -1282,8 +1282,10 @@ static game_state *execute_move(const game_state *state, const char *move)
+ move_params[i] = atoi(p);
+ while (*p && isdigit((unsigned char)*p)) p++;
+ if (i+1 < nparams) {
+- if (*p != ',')
++ if (*p != ',') {
++ free_game(new_state);
+ return NULL;
++ }
+ p++;
+ }
+ }
+@@ -1299,7 +1301,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ }
+ cell = get_coords(new_state, new_state->cells_contents, x, y);
+ if (cell == NULL) {
+- sfree(new_state);
++ free_game(new_state);
+ return NULL;
+ }
+ if (*cell >= STATE_OK_NUM) {
+@@ -1369,7 +1371,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ cell = get_coords(new_state, new_state->cells_contents,
+ x + (dirX * i), y + (dirY * i));
+ if (cell == NULL) {
+- sfree(new_state);
++ free_game(new_state);
+ return NULL;
+ }
+ if ((*cell & STATE_OK_NUM) == 0) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0049-Twiddle-don-t-read-off-the-end-of-parameter-strings-.patch sgt-puzzles-20230122.806ae71/debian/patches/0049-Twiddle-don-t-read-off-the-end-of-parameter-strings-.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0049-Twiddle-don-t-read-off-the-end-of-parameter-strings-.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0049-Twiddle-don-t-read-off-the-end-of-parameter-strings-.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,37 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 14:31:39 +0000
+Subject: [PATCH 049/159] Twiddle: don't read off the end of parameter strings
+ ending 'm'
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=73c7bc090155ab8c4661feaeea9e6a6e74ee6f77
+Bug-Debian: https://bugs.debian.org/1034190
+
+The overrun could be demonstrated by specifying a parameter string of
+"3x3m" to a build with AddressSanitizer.
+---
+ twiddle.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/twiddle.c b/twiddle.c
+index 6d86264c..8c565a04 100644
+--- a/twiddle.c
++++ b/twiddle.c
+@@ -124,14 +124,16 @@ static void decode_params(game_params *ret, char const *string)
+ while (*string) {
+ if (*string == 'r') {
+ ret->rowsonly = true;
++ string++;
+ } else if (*string == 'o') {
+ ret->orientable = true;
++ string++;
+ } else if (*string == 'm') {
+ string++;
+ ret->movetarget = atoi(string);
+- while (string[1] && isdigit((unsigned char)string[1])) string++;
+- }
+- string++;
++ while (*string && isdigit((unsigned char)*string)) string++;
++ } else
++ string++;
+ }
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0050-Loopy-free-the-grid-description-string-if-it-s-inval.patch sgt-puzzles-20230122.806ae71/debian/patches/0050-Loopy-free-the-grid-description-string-if-it-s-inval.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0050-Loopy-free-the-grid-description-string-if-it-s-inval.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0050-Loopy-free-the-grid-description-string-if-it-s-inval.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,31 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 20:17:58 +0000
+Subject: [PATCH 050/159] Loopy: free the grid description string if it's
+ invalid
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=e336513be755159158c5ba017c91b018ad4cd36c
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ loopy.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/loopy.c b/loopy.c
+index 1e041f92..18da2e82 100644
+--- a/loopy.c
++++ b/loopy.c
+@@ -777,10 +777,13 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ * know is the precise number of faces. */
+ grid_desc = extract_grid_desc(&desc);
+ ret = grid_validate_desc(grid_types[params->type], params->w, params->h, grid_desc);
+- if (ret) return ret;
++ if (ret) {
++ sfree(grid_desc);
++ return ret;
++ }
+
+ g = loopy_generate_grid(params, grid_desc);
+- if (grid_desc) sfree(grid_desc);
++ sfree(grid_desc);
+
+ for (; *desc; ++desc) {
+ if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0051-Mosaic-don-t-duplicate-the-description-being-validat.patch sgt-puzzles-20230122.806ae71/debian/patches/0051-Mosaic-don-t-duplicate-the-description-being-validat.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0051-Mosaic-don-t-duplicate-the-description-being-validat.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0051-Mosaic-don-t-duplicate-the-description-being-validat.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,45 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 21:00:11 +0000
+Subject: [PATCH 051/159] Mosaic: don't duplicate the description being
+ validated
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=da2767a3f9bf4abb0436157972366202ad53a407
+Bug-Debian: https://bugs.debian.org/1034190
+
+Mosaic's validate_desc() doesn't write to the description string, so
+it has no need to make a copy of it. And if it doesn't copy it, it
+can't leak the copy.
+---
+ mosaic.c | 13 +++++--------
+ 1 file changed, 5 insertions(+), 8 deletions(-)
+
+diff --git a/mosaic.c b/mosaic.c
+index 74a06d0f..39d368a0 100644
+--- a/mosaic.c
++++ b/mosaic.c
+@@ -832,21 +832,18 @@ static const char *validate_desc(const game_params *params,
+ const char *desc)
+ {
+ int size_dest = params->height * params->width;
+- char *curr_desc = dupstr(desc);
+- char *desc_base = curr_desc;
+ int length;
+ length = 0;
+
+- while (*curr_desc != '\0') {
+- if (*curr_desc >= 'a' && *curr_desc <= 'z') {
+- length += *curr_desc - 'a';
+- } else if (*curr_desc < '0' || *curr_desc > '9')
++ while (*desc != '\0') {
++ if (*desc >= 'a' && *desc <= 'z') {
++ length += *desc - 'a';
++ } else if (*desc < '0' || *desc > '9')
+ return "Invalid character in game description";
+ length++;
+- curr_desc++;
++ desc++;
+ }
+
+- sfree(desc_base);
+ if (length != size_dest) {
+ return "Desc size mismatch";
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0052-Avoid-division-by-zero-in-Cube-grid-size-checks.patch sgt-puzzles-20230122.806ae71/debian/patches/0052-Avoid-division-by-zero-in-Cube-grid-size-checks.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0052-Avoid-division-by-zero-in-Cube-grid-size-checks.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0052-Avoid-division-by-zero-in-Cube-grid-size-checks.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,34 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 00:14:22 +0000
+Subject: [PATCH 052/159] Avoid division by zero in Cube grid-size checks
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=df783b93e3271264a8d54f90876f41a80ef2247d
+Bug-Debian: https://bugs.debian.org/1034190
+
+On a triangular grid, Cube allows either d1 or d2 (but not both) to be
+zero, so it's important to check that each one is not zero before
+dividing by it.
+
+The crash could be triggered by, for instance "cube t0x2".
+---
+ cube.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/cube.c b/cube.c
+index 1969bfb9..99f392aa 100644
+--- a/cube.c
++++ b/cube.c
+@@ -567,9 +567,11 @@ static const char *validate_params(const game_params *params, bool full)
+ * can safely multiply them and compare against the
+ * _remaining_ space.
+ */
+- if ((params->d1 > INT_MAX / params->d1) ||
+- (params->d2 > (INT_MAX - params->d1*params->d1) / params->d2) ||
+- (params->d1*params->d2 > (INT_MAX - params->d1*params->d1 -
++ if ((params->d1 > 0 && params->d1 > INT_MAX / params->d1) ||
++ (params->d2 > 0 &&
++ params->d2 > (INT_MAX - params->d1*params->d1) / params->d2) ||
++ (params->d2 > 0 &&
++ params->d1*params->d2 > (INT_MAX - params->d1*params->d1 -
+ params->d2*params->d2) / params->d2))
+ return "Grid area must not be unreasonably large";
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0055-Validate-that-save-file-values-are-ASCII-mostly.patch sgt-puzzles-20230122.806ae71/debian/patches/0055-Validate-that-save-file-values-are-ASCII-mostly.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0055-Validate-that-save-file-values-are-ASCII-mostly.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0055-Validate-that-save-file-values-are-ASCII-mostly.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,39 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sun, 12 Feb 2023 23:04:12 +0000
+Subject: [PATCH 055/159] Validate that save file values are ASCII (mostly)
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=c3a5a7842eb6c41fb75a8a110a3f2cbc1c8fc5d9
+Bug-Debian: https://bugs.debian.org/1034190
+
+Apart from "SEED" records, all values in save files generated by Puzzles
+should be printable ASCII. This is enforced by assertion in the saving
+code. However, if a save file with non-ASCII move strings (for
+instance) manages to get loaded then these non-ASCII values can cause an
+assertion failure on saving. Instead, the loading code now checks
+values for ASCIIness.
+
+This will not only avoid problems when re-saving files, but will also
+defend the various internal parsers from at least some evil strings. It
+shouldn't invalidate any save files actually generated by Puzzles, but
+it will sadly invalidate some of my fuzzing corpus.
+---
+ midend.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/midend.c b/midend.c
+index 17690959..e71c27e7 100644
+--- a/midend.c
++++ b/midend.c
+@@ -2329,6 +2329,13 @@ static const char *midend_deserialise_internal(
+ goto cleanup;
+ }
+ val[len] = '\0';
++ /* Validate that all values (apart from SEED) are printable ASCII. */
++ if (strcmp(key, "SEED"))
++ for (i = 0; val[i]; i++)
++ if (val[i] < 32 || val[i] >= 127) {
++ ret = "Forbidden characters in saved game file";
++ goto cleanup;
++ }
+
+ if (!started) {
+ if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0056-More-validation-of-solve-moves-in-Flood.patch sgt-puzzles-20230122.806ae71/debian/patches/0056-More-validation-of-solve-moves-in-Flood.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0056-More-validation-of-solve-moves-in-Flood.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0056-More-validation-of-solve-moves-in-Flood.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,31 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 00:00:30 +0000
+Subject: [PATCH 056/159] More validation of solve moves in Flood
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=e8668dc883e940f0852ff4520abc3d30cae90aef
+Bug-Debian: https://bugs.debian.org/1034190
+
+To avoid assertion failures while painting it, we need to ensure that
+the purported solution in a solve move doesn't include filling with the
+current top-left colour at any point. That means checking the first
+entry against the current top-left colours, and each later one against
+its predecessor.
+---
+ flood.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/flood.c b/flood.c
+index f1ac5e1c..7a83e524 100644
+--- a/flood.c
++++ b/flood.c
+@@ -945,6 +945,11 @@ static game_state *execute_move(const game_state *state, const char *move)
+ return NULL;
+ };
+ sol->moves[i] = atoi(p);
++ if (i == 0 ?
++ sol->moves[i] == state->grid[FILLY * state->w + FILLX] :
++ sol->moves[i] == sol->moves[i-1])
++ /* Solution contains a fill with the current colour. */
++ goto badsolve;
+ p += strspn(p, "0123456789");
+ if (*p) {
+ if (*p != ',') goto badsolve;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0058-Make-sure-that-moves-in-Flood-use-only-valid-colours.patch sgt-puzzles-20230122.806ae71/debian/patches/0058-Make-sure-that-moves-in-Flood-use-only-valid-colours.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0058-Make-sure-that-moves-in-Flood-use-only-valid-colours.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0058-Make-sure-that-moves-in-Flood-use-only-valid-colours.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,56 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Tue, 14 Feb 2023 22:02:35 +0000
+Subject: [PATCH 058/159] Make sure that moves in Flood use only valid colours
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=7364ce8e266d947be146d635958a7b282752aac6
+Bug-Debian: https://bugs.debian.org/1034190
+
+If execute_move() receieves a move that uses a colour beyond the range
+for the current game, it now rejects it. Without this a solve string
+containing an invalid colour would cause an assertion failure: "fill:
+Assertion `oldcolour != newcolour' failed." While I was in the area I
+put a range check on colours for normal moves as well. To demonstrate
+the problem, load this save file:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :5:Flood
+PARAMS :7:6x6c6m5
+CPARAMS :7:6x6c6m3
+DESC :39:432242034203340350204502505323231342,17
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :2:S6
+---
+ flood.c | 12 +++++++-----
+ 1 file changed, 7 insertions(+), 5 deletions(-)
+
+diff --git a/flood.c b/flood.c
+index 7a83e524..441119c6 100644
+--- a/flood.c
++++ b/flood.c
+@@ -886,7 +886,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+
+ if (move[0] == 'M' &&
+ sscanf(move+1, "%d", &c) == 1 &&
+- c >= 0 &&
++ c >= 0 && c < state->colours &&
+ c != state->grid[FILLY * state->w + FILLX] &&
+ !state->complete) {
+ int *queue = snewn(state->w * state->h, int);
+@@ -945,10 +945,12 @@ static game_state *execute_move(const game_state *state, const char *move)
+ return NULL;
+ };
+ sol->moves[i] = atoi(p);
+- if (i == 0 ?
+- sol->moves[i] == state->grid[FILLY * state->w + FILLX] :
+- sol->moves[i] == sol->moves[i-1])
+- /* Solution contains a fill with the current colour. */
++ if (sol->moves[i] < 0 || sol->moves[i] >= state->colours ||
++ (i == 0 ?
++ sol->moves[i] == state->grid[FILLY * state->w + FILLX] :
++ sol->moves[i] == sol->moves[i-1]))
++ /* Solution contains a fill with an invalid colour or
++ * the current colour. */
+ goto badsolve;
+ p += strspn(p, "0123456789");
+ if (*p) {
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0059-Tighten-grid-size-limit-in-Mines.patch sgt-puzzles-20230122.806ae71/debian/patches/0059-Tighten-grid-size-limit-in-Mines.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0059-Tighten-grid-size-limit-in-Mines.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0059-Tighten-grid-size-limit-in-Mines.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,48 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Wed, 15 Feb 2023 14:07:34 +0000
+Subject: [PATCH 059/159] Tighten grid-size limit in Mines
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=9394e9c74bdb48dc1c74693bcb41fd35f8fc743c
+Bug-Debian: https://bugs.debian.org/1034190
+
+Mines uses random_upto() to decide where to place mines, and
+random_upto() takes a maximum limit of 2^28-1, so limit the number of
+grid squares to that (or INT_MAX if someone's still trying to build on
+a 16-bit system).
+
+This avoids an assertion failure: "random_upto: Assertion `bits < 32'
+failed." which can be demonstrated by this save file:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :5:Mines
+PARAMS :5:18090
+CPARAMS :5:18090
+DESC :11:r9,u,MEdff6
+UI :2:D0
+TIME :1:0
+NSTATES :1:2
+STATEPOS:1:2
+MOVE :4:O2,1
+---
+ mines.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/mines.c b/mines.c
+index 04364e5a..7ab4dbc3 100644
+--- a/mines.c
++++ b/mines.c
+@@ -265,7 +265,14 @@ static const char *validate_params(const game_params *params, bool full)
+ return "Width and height must both be at least one";
+ if (params->w > SHRT_MAX || params->h > SHRT_MAX)
+ return "Neither width nor height may be unreasonably large";
++ /*
++ * We use random_upto() to place mines, and its maximum limit is 2^28-1.
++ */
++#if (1<<28)-1 < INT_MAX
++ if (params->w > ((1<<28)-1) / params->h)
++#else
+ if (params->w > INT_MAX / params->h)
++#endif
+ return "Width times height must not be unreasonably large";
+ if (params->n < 0)
+ return "Mine count may not be negative";
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0061-Solo-cope-with-pencil-marks-when-tilesize-1.patch sgt-puzzles-20230122.806ae71/debian/patches/0061-Solo-cope-with-pencil-marks-when-tilesize-1.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0061-Solo-cope-with-pencil-marks-when-tilesize-1.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0061-Solo-cope-with-pencil-marks-when-tilesize-1.patch 2023-04-16 21:18:03.000000000 +0200
@@ -0,0 +1,32 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Thu, 16 Feb 2023 15:54:17 +0000
+Subject: [PATCH 061/159] Solo: cope with pencil marks when tilesize == 1
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=3cd51d001769c657ebb4184bd05343af4d7e12b1
+Bug-Debian: https://bugs.debian.org/905852
+
+Solo's layout calculations for pencil marks could fail with a tilesize
+of 1, generating an assertion failure: "draw_number: Assertion `pbest
+> 0' failed." This was reported as Debian bug #905852.
+
+My solution is slightly silly, namely to change a ">" in the test for
+whether a new layout is the best so far to ">=". This allows for
+finding a (terrible) layout even for tilesize == 1, and also has the
+side-effect of slightly preserring wide layouts over tall ones.
+Personally, I think that's an improvement.
+---
+ solo.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/solo.c b/solo.c
+index ff980730..732ca2e5 100644
+--- a/solo.c
++++ b/solo.c
+@@ -5133,7 +5133,7 @@ static void draw_number(drawing *dr, game_drawstate *ds,
+ fw = (pr - pl) / (float)pw;
+ fh = (pb - pt) / (float)ph;
+ fs = min(fw, fh);
+- if (fs > bestsize) {
++ if (fs >= bestsize) {
+ bestsize = fs;
+ pbest = pw;
+ }
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0065-Tracks-set-drag_s-x-y-even-if-starting-off-grid.patch sgt-puzzles-20230122.806ae71/debian/patches/0065-Tracks-set-drag_s-x-y-even-if-starting-off-grid.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0065-Tracks-set-drag_s-x-y-even-if-starting-off-grid.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0065-Tracks-set-drag_s-x-y-even-if-starting-off-grid.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,30 @@
+From: Chris Boyle <chris@boyle.name>
+Date: Fri, 10 Feb 2023 15:23:33 +0000
+Subject: [PATCH 065/159] Tracks: set drag_s{x,y} even if starting off-grid
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=111db0743bd273c340dd683449076fd9ec5797f5
+Bug-Debian: https://bugs.debian.org/1034190
+
+Otherwise, if subsequent mouse/finger movement lines up with the previous
+drag attempt's start, then suddenly a drag is in progress from there, which
+is confusing.
+
+Fixes #588
+
+(cherry picked from Android port,
+commit 8ce1bbe460d70a915caf2dbeb30354d22dc8a8ef)
+---
+ tracks.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/tracks.c b/tracks.c
+index 6948b42c..0579d4bd 100644
+--- a/tracks.c
++++ b/tracks.c
+@@ -2267,6 +2267,7 @@ static char *interpret_move(const game_state *state, game_ui *ui,
+
+ if (!INGRID(state, gx, gy)) {
+ /* can't drag from off grid */
++ ui->drag_sx = ui->drag_sy = -1;
+ return NULL;
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0080-Undead-be-a-bit-more-careful-about-sprintf-buffer-si.patch sgt-puzzles-20230122.806ae71/debian/patches/0080-Undead-be-a-bit-more-careful-about-sprintf-buffer-si.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0080-Undead-be-a-bit-more-careful-about-sprintf-buffer-si.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0080-Undead-be-a-bit-more-careful-about-sprintf-buffer-si.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,33 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 18 Feb 2023 21:26:38 +0000
+Subject: [PATCH 080/159] Undead: be a bit more careful about sprintf buffer
+ sizes
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=448095ede815b1a63ddedc602c3ac768a0d52968
+Bug-Debian: https://bugs.debian.org/1034190
+
+---
+ undead.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/undead.c b/undead.c
+index 6feca640..78abf28e 100644
+--- a/undead.c
++++ b/undead.c
+@@ -2429,7 +2429,7 @@ static void draw_monster(drawing *dr, game_drawstate *ds, int x, int y,
+ static void draw_monster_count(drawing *dr, game_drawstate *ds,
+ const game_state *state, int c, bool hflash) {
+ int dx,dy;
+- char buf[8];
++ char buf[MAX_DIGITS(int) + 1];
+ char bufm[8];
+
+ dy = TILESIZE/4;
+@@ -2474,7 +2474,7 @@ static void draw_path_hint(drawing *dr, game_drawstate *ds,
+ const struct game_params *params,
+ int hint_index, bool hflash, int hint) {
+ int x, y, color, dx, dy, text_dx, text_dy, text_size;
+- char buf[4];
++ char buf[MAX_DIGITS(int) + 1];
+
+ if (ds->hint_errors[hint_index])
+ color = COL_ERROR;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0090-Fix-memory-leak-in-midend_game_id_int.patch sgt-puzzles-20230122.806ae71/debian/patches/0090-Fix-memory-leak-in-midend_game_id_int.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0090-Fix-memory-leak-in-midend_game_id_int.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0090-Fix-memory-leak-in-midend_game_id_int.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,34 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 20 Feb 2023 14:50:22 +0000
+Subject: [PATCH 090/159] Fix memory leak in midend_game_id_int()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=4e09175fdaaffc0483fc9bf767311268794d956c
+Bug-Debian: https://bugs.debian.org/1034190
+
+The "par" string wasn't getting freed on some error paths. Fixed by
+freeing it immediately after its last use, which is before any of the
+error paths.
+---
+ midend.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/midend.c b/midend.c
+index e71c27e7..f566251a 100644
+--- a/midend.c
++++ b/midend.c
+@@ -1801,6 +1801,7 @@ static const char *midend_game_id_int(midend *me, const char *id, int defmode)
+ newcurparams = me->ourgame->default_params();
+ }
+ me->ourgame->decode_params(newcurparams, par);
++ sfree(par);
+ error = me->ourgame->validate_params(newcurparams, desc == NULL);
+ if (error) {
+ me->ourgame->free_params(newcurparams);
+@@ -1876,8 +1877,6 @@ static const char *midend_game_id_int(midend *me, const char *id, int defmode)
+ me->genmode = GOT_SEED;
+ }
+
+- sfree(par);
+-
+ me->newgame_can_store_undo = false;
+
+ return NULL;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0092-Flood-don-t-read-off-the-end-of-some-parameter-strin.patch sgt-puzzles-20230122.806ae71/debian/patches/0092-Flood-don-t-read-off-the-end-of-some-parameter-strin.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0092-Flood-don-t-read-off-the-end-of-some-parameter-strin.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0092-Flood-don-t-read-off-the-end-of-some-parameter-strin.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,37 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 20 Feb 2023 14:57:31 +0000
+Subject: [PATCH 092/159] Flood: don't read off the end of some parameter
+ strings
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=bbe866a3819c6a754a5b1d8c5bc5d0701796acfb
+Bug-Debian: https://bugs.debian.org/1034190
+
+This is essentially the same fix as 73c7bc090155ab8c was for Twiddle.
+The new code is less clever but more correct (and more obviously
+correct). The bug could be demonstrated by using a parameter string
+of "c" or "m" with an AddressSanitizer build of Flood.
+---
+ flood.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/flood.c b/flood.c
+index 441119c6..77eb48ae 100644
+--- a/flood.c
++++ b/flood.c
+@@ -141,13 +141,13 @@ static void decode_params(game_params *ret, char const *string)
+ if (*string == 'c') {
+ string++;
+ ret->colours = atoi(string);
+- while (string[1] && isdigit((unsigned char)string[1])) string++;
++ while (*string && isdigit((unsigned char)*string)) string++;
+ } else if (*string == 'm') {
+ string++;
+ ret->leniency = atoi(string);
+- while (string[1] && isdigit((unsigned char)string[1])) string++;
+- }
+- string++;
++ while (*string && isdigit((unsigned char)*string)) string++;
++ } else
++ string++;
+ }
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0101-Be-more-careful-with-type-of-left-operand-of.patch sgt-puzzles-20230122.806ae71/debian/patches/0101-Be-more-careful-with-type-of-left-operand-of.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0101-Be-more-careful-with-type-of-left-operand-of.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0101-Be-more-careful-with-type-of-left-operand-of.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,49 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sun, 26 Feb 2023 14:24:38 +0000
+Subject: [PATCH 101/159] Be more careful with type of left operand of <<
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=93be3f7ccaa63b0fd953bcfd88d685b47b76605e
+Bug-Debian: https://bugs.debian.org/1034190
+
+On a 32-bit system, evaluating 1<<31 causes undefined behaviour because
+1 is signed and so it produces signed overflow. UBSan has spotted a
+couple of occasions where this happens in Puzzles, so in each case I've
+converted the left operand to the unsigned result type we actually want.
+---
+ cube.c | 4 ++--
+ random.c | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/cube.c b/cube.c
+index 69a9d35e..3b4e9753 100644
+--- a/cube.c
++++ b/cube.c
+@@ -202,8 +202,8 @@ struct game_grid {
+ };
+
+ #define SET_SQUARE(state, i, val) \
+- ((state)->bluemask[(i)/32] &= ~(1 << ((i)%32)), \
+- (state)->bluemask[(i)/32] |= ((!!val) << ((i)%32)))
++ ((state)->bluemask[(i)/32] &= ~(1UL << ((i)%32)), \
++ (state)->bluemask[(i)/32] |= ((unsigned long)(!!val) << ((i)%32)))
+ #define GET_SQUARE(state, i) \
+ (((state)->bluemask[(i)/32] >> ((i)%32)) & 1)
+
+diff --git a/random.c b/random.c
+index 5527d6ff..bd11f166 100644
+--- a/random.c
++++ b/random.c
+@@ -254,12 +254,12 @@ unsigned long random_bits(random_state *state, int bits)
+ }
+
+ /*
+- * `(1 << bits) - 1' is not good enough, since if bits==32 on a
++ * `(1UL << bits) - 1' is not good enough, since if bits==32 on a
+ * 32-bit machine, behaviour is undefined and Intel has a nasty
+ * habit of shifting left by zero instead. We'll shift by
+ * bits-1 and then separately shift by one.
+ */
+- ret &= (1 << (bits-1)) * 2 - 1;
++ ret &= (1UL << (bits-1)) * 2 - 1;
+ return ret;
+ }
+
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0102-Map-reduce-maximum-size.patch sgt-puzzles-20230122.806ae71/debian/patches/0102-Map-reduce-maximum-size.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0102-Map-reduce-maximum-size.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0102-Map-reduce-maximum-size.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,25 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sun, 26 Feb 2023 14:51:09 +0000
+Subject: [PATCH 102/159] Map: reduce maximum size
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=e2d390aae872cee4cb16d746af3b2eeb7713cbf5
+Bug-Debian: https://bugs.debian.org/1034190
+
+validate_desc relies on being able to calculate 2*wh in an int, so the
+maximum grid size is at most INT_MAX/2.
+---
+ map.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/map.c b/map.c
+index 04783449..2d624b97 100644
+--- a/map.c
++++ b/map.c
+@@ -255,7 +255,7 @@ static const char *validate_params(const game_params *params, bool full)
+ {
+ if (params->w < 2 || params->h < 2)
+ return "Width and height must be at least two";
+- if (params->w > INT_MAX / params->h)
++ if (params->w > INT_MAX / 2 / params->h)
+ return "Width times height must not be unreasonably large";
+ if (params->n < 5)
+ return "Must have at least five regions";
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0103-Correctly-handle-some-short-save-files.patch sgt-puzzles-20230122.806ae71/debian/patches/0103-Correctly-handle-some-short-save-files.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0103-Correctly-handle-some-short-save-files.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0103-Correctly-handle-some-short-save-files.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,42 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sun, 26 Feb 2023 21:48:10 +0000
+Subject: [PATCH 103/159] Correctly handle some short save files
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=6ee62a43abe7d7e77226415b21d1cbf16dbda85a
+Bug-Debian: https://bugs.debian.org/1034190
+
+A save file that ended in the middle of a value before the "SAVEFILE"
+field had been loaded would cause a read from uninitialised memory.
+While technically undefined behaviour this was practically pretty
+harmless. Fixed by handling unexpected EOF here the same an
+unexpected EOF anywhere else.
+
+This bug could be demonstrated by loading a truncated save file like
+this in a build with MemorySanitizer enabled:
+
+SAVEFILE:41:Simo
+---
+ midend.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/midend.c b/midend.c
+index 23652583..90bcde27 100644
+--- a/midend.c
++++ b/midend.c
+@@ -2340,7 +2340,7 @@ static const char *midend_deserialise_internal(
+
+ val = snewn(len+1, char);
+ if (!read(rctx, val, len)) {
+- if (started)
++ /* unexpected EOF */
+ goto cleanup;
+ }
+ val[len] = '\0';
+@@ -2747,7 +2747,7 @@ const char *identify_game(char **name,
+
+ val = snewn(len+1, char);
+ if (!read(rctx, val, len)) {
+- if (started)
++ /* unexpected EOF */
+ goto cleanup;
+ }
+ val[len] = '\0';
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0104-Inertia-insist-that-solutions-must-be-non-empty.patch sgt-puzzles-20230122.806ae71/debian/patches/0104-Inertia-insist-that-solutions-must-be-non-empty.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0104-Inertia-insist-that-solutions-must-be-non-empty.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0104-Inertia-insist-that-solutions-must-be-non-empty.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,31 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sun, 26 Feb 2023 23:18:44 +0000
+Subject: [PATCH 104/159] Inertia: insist that solutions must be non-empty
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=5a491c5ad333ef34c1e7713f920f51cbb205af60
+Bug-Debian: https://bugs.debian.org/1034190
+
+Any solution actually generated by the solver will contain at least one
+move, because it refuses to solve games that are already solved.
+However, a save file might contain an empty "solve" move. This causes
+an uninitialised read when execute_move() then tries to check if the
+next move is in accordance with the solution, because the check for
+running off the end of the solution happens after that.
+
+We now avoid this by treating a zero-length "solution" as an invalid
+move.
+---
+ inertia.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/inertia.c b/inertia.c
+index ed50c7a8..1a958af7 100644
+--- a/inertia.c
++++ b/inertia.c
+@@ -1697,6 +1697,7 @@ static game_state *execute_move(const game_state *state, const char *move)
+ * This is a solve move, so we don't actually _change_ the
+ * grid but merely set up a stored solution path.
+ */
++ if (move[1] == '\0') return NULL; /* Solution must be non-empty. */
+ ret = dup_game(state);
+ install_new_solution(ret, move);
+ return ret;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0115-Galaxies-fix-recursion-depth-limit-in-solver.patch sgt-puzzles-20230122.806ae71/debian/patches/0115-Galaxies-fix-recursion-depth-limit-in-solver.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0115-Galaxies-fix-recursion-depth-limit-in-solver.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0115-Galaxies-fix-recursion-depth-limit-in-solver.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,91 @@
+From: Simon Tatham <anakin@pobox.com>
+Date: Sun, 12 Mar 2023 14:11:34 +0000
+Subject: [PATCH 115/159] Galaxies: fix recursion depth limit in solver.
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=f018ef97d34b9126744e63cc2112c302fcae4ab4
+Bug-Debian: https://bugs.debian.org/1034190
+
+The static variable 'solver_recurse_depth' is _mostly_ used by the
+standalone solver, to appropriately indent the solver diagnostics for
+the current recursion level. So most uses of it are guarded by an
+'#ifdef STANDALONE_SOLVER' statement, or some equivalent (such as
+being inside the solvep() macro).
+
+One exception is the check that limits the recursion depth to 5, to
+avoid getting hung up forever on a too-hard game. Unfortunately, this
+check depends on the variable actually incrementing when we recurse
+another level - and it wasn't, because the increment itself was under
+ifdef! So the generator in live Galaxies could recurse arbitrarily
+deep, and generate puzzles that the standalone solver found too hard
+_even_ at Unreasonable mode.
+
+Removed the ifdefs, so that solver_recurse_depth is now incremented
+and decremented. Also, make sure to initialise the depth to 0 at the
+start of a solver run, just in case it had a bogus value left over
+from a previous run.
+---
+ galaxies.c | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+diff --git a/galaxies.c b/galaxies.c
+index 0db0d544..fc20b346 100644
+--- a/galaxies.c
++++ b/galaxies.c
+@@ -187,6 +187,7 @@ struct game_state {
+ };
+
+ static bool check_complete(const game_state *state, int *dsf, int *colours);
++static int solver_state_inner(game_state *state, int maxdiff);
+ static int solver_state(game_state *state, int maxdiff);
+ static int solver_obvious(game_state *state);
+ static int solver_obvious_dot(game_state *state, space *dot);
+@@ -2415,9 +2416,7 @@ static int solver_recurse(game_state *state, int maxdiff)
+ solver_recurse_depth*4, "",
+ rctx.best->x, rctx.best->y, rctx.bestn));
+
+-#ifdef STANDALONE_SOLVER
+ solver_recurse_depth++;
+-#endif
+
+ ingrid = snewn(gsz, space);
+ memcpy(ingrid, state->grid, gsz * sizeof(space));
+@@ -2432,7 +2431,7 @@ static int solver_recurse(game_state *state, int maxdiff)
+ state->dots[n]->x, state->dots[n]->y,
+ "Attempting for recursion");
+
+- ret = solver_state(state, maxdiff);
++ ret = solver_state_inner(state, maxdiff);
+
+ if (diff == DIFF_IMPOSSIBLE && ret != DIFF_IMPOSSIBLE) {
+ /* we found our first solved grid; copy it away. */
+@@ -2464,9 +2463,7 @@ static int solver_recurse(game_state *state, int maxdiff)
+ break;
+ }
+
+-#ifdef STANDALONE_SOLVER
+ solver_recurse_depth--;
+-#endif
+
+ if (outgrid) {
+ /* we found (at least one) soln; copy it back to state */
+@@ -2477,7 +2474,7 @@ static int solver_recurse(game_state *state, int maxdiff)
+ return diff;
+ }
+
+-static int solver_state(game_state *state, int maxdiff)
++static int solver_state_inner(game_state *state, int maxdiff)
+ {
+ solver_ctx *sctx = new_solver(state);
+ int ret, diff = DIFF_NORMAL;
+@@ -2544,6 +2541,12 @@ static int solver_state(game_state *state, int maxdiff)
+ return diff;
+ }
+
++static int solver_state(game_state *state, int maxdiff)
++{
++ solver_recurse_depth = 0;
++ return solver_state_inner(state, maxdiff);
++}
++
+ #ifndef EDITOR
+ static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, const char **error)
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0138-Correct-a-range-check-in-Magnets-layout-verification.patch sgt-puzzles-20230122.806ae71/debian/patches/0138-Correct-a-range-check-in-Magnets-layout-verification.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0138-Correct-a-range-check-in-Magnets-layout-verification.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0138-Correct-a-range-check-in-Magnets-layout-verification.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,26 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Fri, 31 Mar 2023 20:38:31 +0100
+Subject: [PATCH 138/159] Correct a range check in Magnets' layout verification
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=91735e5019be84d2fa693c5d40746c818ace28f8
+Bug-Debian: https://bugs.debian.org/1034190
+
+Squares in the grid are numbered from 0, so the upper limit check
+needs to use "<=" rather than "<". Without this, invalid descriptions
+can cause a read overrun off the end of the board.
+---
+ magnets.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/magnets.c b/magnets.c
+index d190848a..d95d1464 100644
+--- a/magnets.c
++++ b/magnets.c
+@@ -520,7 +520,7 @@ static game_state *new_game_int(const game_params *params, const char *desc,
+ * (i.e. each end points to the other) */
+ for (idx = 0; idx < state->wh; idx++) {
+ if (state->common->dominoes[idx] < 0 ||
+- state->common->dominoes[idx] > state->wh ||
++ state->common->dominoes[idx] >= state->wh ||
+ state->common->dominoes[state->common->dominoes[idx]] != idx) {
+ *prob = "Domino descriptions inconsistent";
+ goto done;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0139-Magnets-add-a-check-that-magnets-don-t-wrap-between-.patch sgt-puzzles-20230122.806ae71/debian/patches/0139-Magnets-add-a-check-that-magnets-don-t-wrap-between-.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0139-Magnets-add-a-check-that-magnets-don-t-wrap-between-.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0139-Magnets-add-a-check-that-magnets-don-t-wrap-between-.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,33 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Sat, 1 Apr 2023 18:54:29 +0100
+Subject: [PATCH 139/159] Magnets: add a check that magnets don't wrap between
+ lines
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=0bd1a8057841386754f9f4a8a268616c7ce80e80
+Bug-Debian: https://bugs.debian.org/1034190
+
+There was nothing in Magnet's description validation to prevent there
+being the left end of a magnet at the right end of a row and the right
+end of a magnet at the left end of the row below. Indeed as far as I
+can such a game (e.g. 3x3:..2,2..,...,1.1,TLRB*LRLR) plays entirely
+correctly except that one magnet is discontinuous.
+
+While this worked, it was entirely an artefact of the particular memory
+layout that Magnets uses and shouldn't have been allowed, so I've added
+an additional validation rule to stop it.
+---
+ magnets.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/magnets.c b/magnets.c
+index d95d1464..06699784 100644
+--- a/magnets.c
++++ b/magnets.c
+@@ -521,6 +521,8 @@ static game_state *new_game_int(const game_params *params, const char *desc,
+ for (idx = 0; idx < state->wh; idx++) {
+ if (state->common->dominoes[idx] < 0 ||
+ state->common->dominoes[idx] >= state->wh ||
++ (state->common->dominoes[idx] % state->w != idx % state->w &&
++ state->common->dominoes[idx] / state->w != idx / state->w) ||
+ state->common->dominoes[state->common->dominoes[idx]] != idx) {
+ *prob = "Domino descriptions inconsistent";
+ goto done;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0155-Net-assert-that-cx-and-cy-are-in-range-in-compute_ac.patch sgt-puzzles-20230122.806ae71/debian/patches/0155-Net-assert-that-cx-and-cy-are-in-range-in-compute_ac.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0155-Net-assert-that-cx-and-cy-are-in-range-in-compute_ac.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0155-Net-assert-that-cx-and-cy-are-in-range-in-compute_ac.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,40 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Mon, 13 Feb 2023 22:14:26 +0000
+Subject: [PATCH 155/159] Net: assert that cx and cy are in range in
+ compute_active()
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=e411db788cfc0d0ed54b3c9b9deb15edba7d237a
+Bug-Debian: https://bugs.debian.org/1034190
+
+This avoids an out-of-range heap write shortly afterwards. An assertion
+failure is better than a buffer overrun, but still not ideal. Fixing
+the problem properly will require fairly wide-ranging changes, though.
+
+The bug can be demonstrated by loading this save file into a build with
+AddressSanitizer:
+
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME :3:Net
+PARAMS :4:5x5w
+CPARAMS :4:5x5w
+DESC :25:9893e85285bb72e6de5182741
+UI :9:O0,0;C6,6
+NSTATES :1:1
+STATEPOS:1:1
+---
+ net.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net.c b/net.c
+index 6f7d1498..7f900472 100644
+--- a/net.c
++++ b/net.c
+@@ -1872,6 +1872,8 @@ static unsigned char *compute_active(const game_state *state, int cx, int cy)
+ active = snewn(state->width * state->height, unsigned char);
+ memset(active, 0, state->width * state->height);
+
++ assert(0 <= cx && cx < state->width);
++ assert(0 <= cy && cy < state->height);
+ /*
+ * We only store (x,y) pairs in todo, but it's easier to reuse
+ * xyd_cmp and just store direction 0 every time.
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/0159-Don-t-allow-zero-clues-in-Pattern.patch sgt-puzzles-20230122.806ae71/debian/patches/0159-Don-t-allow-zero-clues-in-Pattern.patch
--- sgt-puzzles-20230122.806ae71/debian/patches/0159-Don-t-allow-zero-clues-in-Pattern.patch 1970-01-01 01:00:00.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/0159-Don-t-allow-zero-clues-in-Pattern.patch 2023-04-16 21:15:13.000000000 +0200
@@ -0,0 +1,36 @@
+From: Ben Harris <bjh21@bjh21.me.uk>
+Date: Tue, 14 Feb 2023 13:16:53 +0000
+Subject: [PATCH 159/159] Don't allow zero clues in Pattern
+Origin: https://git.tartarus.org/?p=simon/puzzles.git;a=commitdiff;h=71cf891fdc3ab237ecf0e5d1aae39b6c9fe97a4d
+Bug-Debian: https://bugs.debian.org/1034190
+
+Some nonogram implementations allow zero clues so that a row or column
+with a single zero clue is equivalent to one with no clues, that is it
+has no black squares in it. Pattern, however, doesn't interpret them
+like this and treats a puzzle with a zero clue as insoluble, so it's
+not helpful to permit them.
+
+Permitting zero clues also confuses Pattern's memory allocation so
+that it can suffer a buffer overrun. As an example, before this
+commit a build with AddressSanitizer would report a buffer overrun
+with the description "1:0/0.0" because it tries to put two clues in a
+row that can have a maximum of one.
+---
+ pattern.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pattern.c b/pattern.c
+index 4fd67530..46025856 100644
+--- a/pattern.c
++++ b/pattern.c
+@@ -917,8 +917,8 @@ static const char *validate_desc(const game_params *params, const char *desc)
+ p = desc;
+ while (*desc && isdigit((unsigned char)*desc)) desc++;
+ n = atoi(p);
+- if (n < 0)
+- return "at least one clue is negative";
++ if (n <= 0)
++ return "all clues must be positive";
+ if (n > INT_MAX - 1)
+ return "at least one clue is grossly excessive";
+ rowspace -= n+1;
diff -Nru sgt-puzzles-20230122.806ae71/debian/patches/series sgt-puzzles-20230122.806ae71/debian/patches/series
--- sgt-puzzles-20230122.806ae71/debian/patches/series 2023-01-22 23:21:38.000000000 +0100
+++ sgt-puzzles-20230122.806ae71/debian/patches/series 2023-04-16 21:19:11.000000000 +0200
@@ -6,3 +6,63 @@
207_slant-shade-filled.diff
303_show-debian-version-number.diff
install-two-icon-sizes.patch
+0001-Black-Box-reject-negative-ball-counts-in-game_params.patch
+0002-Add-validate_params-bounds-checks-in-a-few-more-game.patch
+0006-Don-t-allow-Bridges-games-with-2-islands.patch
+0007-Forbid-moves-that-fill-with-the-current-colour-in-Fl.patch
+0008-Cleanly-reject-ill-formed-solve-moves-in-Flood.patch
+0009-Don-t-segfault-on-premature-solve-moves-in-Mines.patch
+0010-Limit-number-of-mines-in-Mines-game-description.patch
+0011-Validate-the-number-of-pegs-and-holes-in-a-Pegs-game.patch
+0017-Mines-forbid-moves-that-flag-or-unflag-an-exposed-sq.patch
+0018-Mines-Don-t-check-if-the-player-has-won-if-they-ve-a.patch
+0019-Avoid-invalid-moves-when-solving-Tracks.patch
+0020-Fix-move-validation-in-Netslide.patch
+0021-Tighten-validation-of-Tents-game-descriptions.patch
+0022-Dominosa-require-the-two-halves-of-a-domino-to-be-ad.patch
+0023-Forbid-lines-off-the-grid-in-Pearl.patch
+0024-Tolerate-incorrect-solutions-in-Inertia.patch
+0025-Palisade-replace-dfs_dsf-with-a-simple-iteration.patch
+0026-latin_solver_alloc-handle-clashing-numbers-in-input-.patch
+0027-Pearl-fix-assertion-failure-on-bad-puzzle.patch
+0028-Pearl-fix-bounds-check-in-previous-commit.patch
+0029-Unequal-Don-t-insist-that-solve-moves-must-actually-.patch
+0030-Range-Don-t-fail-an-assertion-on-an-all-black-board.patch
+0031-Limit-width-and-height-to-SHRT_MAX-in-Mines.patch
+0032-Mines-Add-assertions-to-range-check-conversions-to-s.patch
+0033-Unequal-fix-sense-error-in-latin_solver_alloc-fix.patch
+0034-Forbid-impossible-moves-in-Bridges.patch
+0035-Forbid-game-descriptions-with-joined-islands-in-Brid.patch
+0037-Check-state-is-valid-at-the-end-of-a-move-in-Pearl.patch
+0038-Cleanly-reject-more-ill-formed-solve-moves-in-Flood.patch
+0039-Don-t-allow-moves-that-change-the-constraints-in-Une.patch
+0041-Fix-memory-leaks-in-Keen-s-validate_desc.patch
+0042-Remember-to-free-the-actual_board-array-in-Mosaic.patch
+0043-Don-t-leak-grids-in-Loopy-s-validate_desc.patch
+0044-Remember-to-free-the-to_draw-member-from-Net-s-draws.patch
+0045-Undead-check-the-return-value-of-sscanf-in-execute_m.patch
+0046-Don-t-leak-duplicate-edges-in-Untangle.patch
+0047-Remember-to-free-the-numcolours-array-from-Pattern-s.patch
+0048-Free-new-game_state-properly-in-Mosaic-s-execute_mov.patch
+0049-Twiddle-don-t-read-off-the-end-of-parameter-strings-.patch
+0050-Loopy-free-the-grid-description-string-if-it-s-inval.patch
+0051-Mosaic-don-t-duplicate-the-description-being-validat.patch
+0052-Avoid-division-by-zero-in-Cube-grid-size-checks.patch
+0055-Validate-that-save-file-values-are-ASCII-mostly.patch
+0056-More-validation-of-solve-moves-in-Flood.patch
+0058-Make-sure-that-moves-in-Flood-use-only-valid-colours.patch
+0059-Tighten-grid-size-limit-in-Mines.patch
+0061-Solo-cope-with-pencil-marks-when-tilesize-1.patch
+0065-Tracks-set-drag_s-x-y-even-if-starting-off-grid.patch
+0080-Undead-be-a-bit-more-careful-about-sprintf-buffer-si.patch
+0090-Fix-memory-leak-in-midend_game_id_int.patch
+0092-Flood-don-t-read-off-the-end-of-some-parameter-strin.patch
+0101-Be-more-careful-with-type-of-left-operand-of.patch
+0102-Map-reduce-maximum-size.patch
+0103-Correctly-handle-some-short-save-files.patch
+0104-Inertia-insist-that-solutions-must-be-non-empty.patch
+0115-Galaxies-fix-recursion-depth-limit-in-solver.patch
+0138-Correct-a-range-check-in-Magnets-layout-verification.patch
+0139-Magnets-add-a-check-that-magnets-don-t-wrap-between-.patch
+0155-Net-assert-that-cx-and-cy-are-in-range-in-compute_ac.patch
+0159-Don-t-allow-zero-clues-in-Pattern.patch
Reply to: