[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1034636: marked as done (unblock: sgt-puzzles/20230122.806ae71-2)



Your message dated Thu, 20 Apr 2023 17:50:12 +0200
with message-id <1a55ad9a-82b8-f855-8d25-913650a9c46f@debian.org>
and subject line Re: Bug#1034636: unblock: sgt-puzzles/20230122.806ae71-2
has caused the Debian Bug report #1034636,
regarding unblock: sgt-puzzles/20230122.806ae71-2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1034636: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1034636
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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

--- End Message ---
--- Begin Message ---
Hi Ben,

On 20-04-2023 15:33, Ben Hutchings wrote:
The changes for this look large, but most patches only change a few
lines.  Combined diffstat for the new patches is:

Thanks for this.

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.

Ack.

unblock sgt-puzzles/20230122.806ae71-2

done.

Paul

Attachment: OpenPGP_signature
Description: OpenPGP digital signature


--- End Message ---

Reply to: