Control: retitle -1 bsdgames: sail, adventure and trek misbehave where plain 
char is unsigned (arm64, armhf, ppc64el, s390x, riscv64)

Dear Maintainer,

after reporting sail I went through the rest of the package for the same
assumption.  Two more games are affected; a second patch is attached
(0024-..., DEP-3, applies with -p1 on top of the sail patch or without it).

adventure (bsdgames-adventure): end of input is never noticed.

    $ printf 'no\n' | /usr/games/bsdgames-adventure | head -c 100000 | grep -c 
'Give me a break'
    2479

  getin() in adventure/io.c stores getchar() in a char and then has "case EOF:"
  in a switch on that char.  With unsigned char the case is unreachable, so on
  ^D, or when piped input runs out, the game prints "Give me a break!!" and
  "I don't know that word." for ever (at full speed) instead of "user closed
  input stream, quitting...".  The same source built with -fsigned-char prints
  that message and exits 0.

trek: a lost game gets the bonus for winning.

  Start trek, take a short novice game, and "destruct".  On arm64 the score is

    Penalty for 5 klingons remaining         -2000
    Bonus for winning a novice game            100
    Penalty for getting killed                -500

  Move.endgame (trek/trek.h) is a plain char; lose() sets it to -1, win() to 1,
  and score() tests "Move.endgame > 0".  255 > 0.  Built with -fsigned-char the
  bonus line is absent and the total is 100 lower.

How I looked, so that you can judge what may have been missed:

  * every game built with gcc 14 and -funsigned-char -Wtype-limits
    -Wswitch-outside-range: the only relevant diagnostics in the whole tree
    are sail/sync.c:260 and adventure/io.c:88 (both "case EOF" on a char);
    no comparison of a plain char with a negative constant or with EOF exists
    anywhere else;
  * every source file through -fsyntax-only -funsigned-char -Wsign-conversion,
    looking for negative constants converted to char: 93 in sail/globals.c
    (the tables of the first patch) and one in trek/lose.c (endgame = -1);
  * hack keeps its small signed numbers in "schar", which config.h already
    defines as short int for exactly this reason;
  * the 230-odd remaining scalar plain-char variables and the char arrays
    that are assigned differences at run time were read through: letters,
    symbols, flags and counts that never go negative;
  * adventure and trek were then built twice, with -fsigned-char and with
    -funsigned-char, and behave differently exactly as described; with the
    patch the unsigned build behaves like the signed one.

Not related to signedness, but seen on the way: several games loop at end of
input whatever the architecture (monop asks "How many players?" for ever,
atc, backgammon, teachgammon and snake likewise fill the terminal).

Attachment: 0024-adventure-trek-plain-char-is-unsigned-on-arm.patch
Description: Binary data

# Other bsdgames and plain char signedness (audit of 2026-09-20)

Source: bsdgames 2.17 + Debian's 22 patches, all 40-odd games Debian builds
(hunt, dm, fortune, factor are not built and were not looked at).

| Game | Finding | Confirmed |
|---|---|---|
| sail | tables of signed values in plain char; `case EOF` on a char | yes, filed |
| adventure | `getin()`: `switch (*s = getchar())` … `case EOF:` unreachable → endless "Give me a break!!" at end of input | packaged binary (2479 repeats in 100 kB) and signed/unsigned builds |
| trek | `char endgame` set to -1 on losing, tested `> 0` → a lost game gets "Bonus for winning" (+100 × skill) | packaged binary and signed/unsigned builds |
| canfield (cfscores) | `pw_uid < 0` always false — uid_t, nothing to do with char | harmless |
| everything else | nothing found | — |

Method: (1) full build with `-funsigned-char -Wextra -Wtype-limits
-Wswitch-outside-range -Wchar-subscripts` (311 objects): three diagnostics in
all; (2) `-fsyntax-only -funsigned-char -Wsign-conversion` on every file with
`LC_ALL=C`, keeping "to 'char' changes value from '-": sail/globals.c ×93,
trek/lose.c ×1 (the detector was first checked against the known sail bug);
(3) typedefs: hack's `schar` is already `short int`; (4) the 234 scalar
plain-char declarations and the char arrays assigned differences at run time,
read by hand; (5) whole tree built twice (signed / unsigned) and the two
suspects compared in behaviour; patched unsigned build re-tested.

Limits: a plain char that goes negative only through arithmetic and is then
used without any comparison against a negative constant would escape (1) and
(2); (4) was a reading, not a proof. Games were not played through.

Files: `0024-adventure-trek-plain-char-is-unsigned-on-arm.patch` (13 changed
lines), `followup.txt` (mail to the existing bug; put the bug number in).

Also seen, unrelated to signedness: monop, atc, backgammon, teachgammon and
snake loop for ever at end of input on any architecture.

Attachment: chartest.c
Description: Binary data

Reply via email to