-
Notifications
You must be signed in to change notification settings - Fork 55
Comparing changes
Open a pull request
base repository: dasm-assembler/dasm
base: bugfix
head repository: dasm-assembler/dasm
compare: master
- 13 commits
- 48 files changed
- 2 contributors
Commits on Jun 8, 2024
-
Configuration menu - View commit details
-
Copy full SHA for 57fd734 - Browse repository at this point
Copy the full SHA 57fd734View commit details
Commits on Jun 25, 2026
-
Fix multiple memory safety, correctness, and robustness bugs
This commit addresses a systematic audit of the DASM source across src/globals.c, src/exp.c, src/main.c, src/ops.c, and src/mnef8.c. Changes fall into four categories: buffer overflows/overreads, memory leaks, logic errors, and silent error suppression. F8-specific bugs in mnef8.c are annotated with comments but left unmodified pending a dedicated fix. --- globals.c --- Avbuf too small (crash on long input lines) Avbuf was declared as char Avbuf[512]. parse() packs the label, mnemonic, and operand fields from the source line sequentially into this buffer. With MAXLINE=1024, the three fields combined can reach 3*MAXLINE bytes, overflowing Avbuf[512] by up to ~2.5 kB on a maximally long input line. Fix: char Avbuf[MAXLINE * 3]. Cvt[] and Opsize[] too short (out-of-bounds access for address modes 22-26) NUMOC was increased to 27 (address modes AM_IMP..AM_OTHER_ENDIAN, indices 0-26) when AM_OTHER_ENDIAN was added, but Cvt[] and Opsize[] were only 22 entries long. v_mnemonic() indexes both arrays directly by addrmode without bounds checks. Any instruction using address modes 22-26 would read uninitialised memory. Fix: extend both arrays to 27 entries with appropriate zero-fill for the new modes. --- exp.c --- pushsymbol() infinite recursion (missing return after asmerr) The recursion depth guard called asmerr(ERROR_RECURSION_TOO_DEEP, true) and then fell through into the rest of the function body rather than returning. With bAbort=true, asmerr normally calls exit(), but if the error is non-fatal in some contexts the fall-through continued the recursion. Added symbolRecursionCount-- and return str + 1 immediately after the asmerr call to guarantee the function exits cleanly. op_mod() silent division-by-zero When the divisor v2 was zero, the modulo operator silently pushed v1 unchanged onto the argument stack (equivalent to returning the dividend as the result). No error was raised and assembly continued with a meaningless value. Fix: call asmerr(ERROR_DIVISION_BY_0, true, NULL) and push 0 as the result, matching the behaviour of op_div(). stackarg() silently discards expression table overflow When the expression evaluator's argument stack filled to MAXARGS, it printed a bare puts() message and wrapped the stack pointer back to Argibase. This silently corrupted the in-flight expression without failing the assembly. Fix: call asmerr(ERROR_EXPRESSION_TABLE_OVERFLOW, true, NULL) so the pass aborts cleanly. --- main.c --- cleanup() stack overflow via deep recursion and tempbuf too small cleanup() was tail-recursive: each comment-processing iteration called itself, with no limit on depth and a stack frame containing a local tempbuf[MAXLINE]. A source file with many nested or sequential comments could exhaust the stack. Additionally, tempbuf was sized at MAXLINE bytes. The snprintf into tempbuf concatenates up to two MAXLINE-length strings (the pre- and post-comment fragments), so tempbuf needed MAXLINE*2 bytes. Fix: convert to an iterative while(1)/continue loop. Size both tempbuf locals at MAXLINE*2. outlistfile() buffer overflow on long source lines buf1 and buf2 were static char[MAXLINE+32]. outlistfile() writes a prefix (~42 bytes of line number, PC, and hex bytes), then appends label + mnemonic + operands + a possible comment via sprintf. On a near-maximum-length source line the total content can exceed MAXLINE+32 by ~20 bytes, overflowing into adjacent static storage. Fix: increase both buffers to char[MAXLINE*2+64]. sftos() rotating buffer had only 2 slots, needed 4 sftos() maintains a rotating static buffer so callers can use its return value in a single printf with multiple calls. The buffer had 2 slots, but ShowSegments() calls sftos() 4 times in one printf invocation (initorg, initrorg, org, rorg). Slots 0 and 1 were overwritten before printf consumed them, producing garbled output. Fix: expand to 4 slots: static char buf[4][MAX_SYM_LEN+14] with slot = (slot + 1) % 4. findmne() stack buffer overflow for long macro names findmne() copies the lowercased mnemonic into char buf[64] with no bounds check. User-defined macro names are limited only by MAX_SYM_LEN (1024). A macro name longer than 63 characters overflows buf[64] into the surrounding stack frame. Fix: char buf[MAX_SYM_LEN + 2]. parse() memory leak: symarg not freed in label comma-arg block When parsing a label with comma-delimited arguments (e.g. label,expr), eval() was called to produce symarg, its value was used, but FreeSymbolList(symarg) was never called. This leaked one or more SYMBOL nodes per such label per assembly pass. Fix: call FreeSymbolList(symarg) after the if(symarg) block. asmerr() buffer overflows in erroradd1 and erroradd2 (3+2 sites) erroradd1[500] and erroradd2[500] are static 500-byte buffers used to accumulate the formatted error message for the pass buffer. - erroradd1 was written with sprintf(erroradd1, "... %s ...", name) where name is a source file path. A filename longer than ~490 bytes overflows erroradd1. - erroradd2 was written with sprintf(erroradd2, str, sText) where sText is caller-supplied context such as a symbol name or filename, which can reach MAX_SYM_LEN (1024) bytes. Fix: replace all five sprintf calls with snprintf(buf, sizeof(buf), ...) to clamp output to the buffer size. CompareAddress() and CompareOrder() undefined behaviour on overflow Both comparison functions returned sym1->value - sym2->value (a long subtraction). For values near LONG_MIN/LONG_MAX the subtraction overflows, producing undefined behaviour and incorrect qsort ordering. Fix: return (a > b) - (a < b), the standard safe three-way comparison. passbuffer_update() off-by-one in realloc size calculation newsizerequired was computed as strlen(existing) + strlen(new) with no +1 for the null terminator. On an exact-fit realloc the null byte was written one past the end of the allocation. Fix: add +1. Macro imbalance diagnostic wrote to listfile instead of stderr The check for unbalanced mac/endm pairs at end of assembly used a locally-declared error_file pointing to FI_listfile or stdout, then printed the diagnostic there. This buried the message in the listing output rather than on the error stream. Fix: write directly to stderr. argVal declared as int for -m option (should be long) The -m command-line option stores its result in argVal via atol(). argVal was declared as int, truncating the value on platforms where sizeof(long) > sizeof(int), and causing the > 64 threshold check to behave incorrectly for large values. Fix: declare argVal as long. --- ops.c --- v_echo() sprintf overflow v_echo() used sprintf(buf, "%s", s->string) and sprintf(buf, "$%lx", s->value) into buf[256]. A string symbol value longer than 255 bytes overflows buf. Fix: snprintf(buf, sizeof(buf), ...) at both sites. generate() uses wrong error code for file-size-exceeded condition When the output segment exceeded maxFileSize, the code called asmerr(ERROR_RECURSION_TOO_DEEP, ...) producing the message "Recursion too deep in ..." for a condition that has nothing to do with recursion. Fix: use ERROR_AVOID_SEGFAULT ("Internal error in %s") which is generic enough to be accurate, and use snprintf for errMsg to avoid a trailing newline being embedded mid-message. v_set() buffer overflow in comma-arg label concatenation v_set() builds a dynamic symbol name in dynamicname[257] by concatenating evaluated argument values. The strcpy(dynamicname+j, tempval) had no bounds check on j, so sufficient comma arguments could overflow dynamicname. Fix: guard the strcpy with if (j + strlen(tempval) < 256). v_set() memory leak: symarg not freed in comma-arg loop Same pattern as the parse() leak above: eval(tempbuf, 0) was called inside the comma-arg while loop and symarg was used but never passed to FreeSymbolList(). Fix: call FreeSymbolList(symarg) at the bottom of the if(symarg) block (combined with the bounds-check fix above). v_incbin() wrong Glen when Redo is set and skip_bytes > 0 The Redo optimisation path estimated the binary file's contribution to the segment size as ftell(binfile) (total file length). When a skip offset was specified (incbin file,N), the correct size is total_length - skip_bytes. Passing the full file length to generate() advanced the segment origin by too many bytes, producing spurious phase errors on subsequent passes. Fix: Glen = ftell(binfile) - skip_bytes, clamped to >= 0. pfopen() fixed 512-byte path buffer overflows for long incdir+filename pfopen() allocated a fixed 512-byte buffer for constructing candidate search paths (incdir + "/" + filename). addpart() writes into this buffer with unchecked strcpy. A long INCDIR path combined with a long filename easily exceeds 511 bytes. Fix: compute the maximum required length by iterating incdirlist before allocating, and zmalloc() exactly that much. --- mnef8.c (comments only, no code changes) --- Four bugs in the F8 backend are documented with BUG comments at the relevant sites: v_ins_outs() and v_lis(): parse_value() return value is ignored. If the operand expression is unresolved on the first pass, parse_value() increments Redo but the function falls through and emits the opcode with operand=0 rather than deferring. Fix requires checking the return value and returning early. v_lr(): reg_dst and reg_src are declared as unsigned char, but parse_special_register() returns int with sentinel REG_NONE=29. The truncation to unsigned char is accidentally safe for REG_NONE (0x1d fits), but would silently break if the sentinel ever exceeded 255. Fix requires declaring both as int. generate_branch(): branch displacement is computed as target - PC - 1, but F8 branch instructions are 2 bytes wide so the displacement should be measured from PC + 2, not PC + 1. All F8 branches land one byte short of their target. Fix: change to target_adr - getPC() - 2.Configuration menu - View commit details
-
Copy full SHA for 8fc7846 - Browse repository at this point
Copy the full SHA 8fc7846View commit details -
Configuration menu - View commit details
-
Copy full SHA for 8ccbc74 - Browse repository at this point
Copy the full SHA 8ccbc74View commit details -
Configuration menu - View commit details
-
Copy full SHA for 8b46964 - Browse repository at this point
Copy the full SHA 8b46964View commit details -
Configuration menu - View commit details
-
Copy full SHA for 6f78606 - Browse repository at this point
Copy the full SHA 6f78606View commit details -
Fix safety/correctness bugs, add include guards, per-platform build dirs
Build system - Makefile: compile objects into .objs/<OS>/ to prevent Mac Mach-O and Linux ELF .o files clobbering each other when the source tree is shared - .gitignore: add src/.objs/ - errors.c: add missing #include "util.h" so getprogname/strlcat prototypes are visible on Linux (was causing implicit-declaration warnings) - errors.h: declare panic() so all TUs can call it without implicit declaration; move forward-decl out of main.c - asm.h: add missing include guard (#ifndef _DASM_ASM_H) — its absence caused duplicate enum errors whenever two headers both included asm.h - symbols.h: rewrite to match reality; previous version was an aspirational redesign with snake_case names, wrong sortmode_t values, and functions that don't exist in symbols.c - version.h: fix DASM_PRINT_LEGAL macro — DASM_PRINT_COPYRIGHT(void) expanded to (void)puts("...")(void), a compile error; remove stray (void) Memory safety - main.c cleanup(): widen two char buf[MAXLINE] to char buf[MAXLINE*2]; comment reformatting can produce up to MAXLINE+2 bytes - main.c outlistfile(): replace sprintf with snprintf, passing remaining buffer length to prevent overflow - main.c bStopAtEnd: fix memset byte count (was nMaxPasses+1, missing *sizeof(bool)); add NULL check and positive-value validation for -p - main.c parse(): add AVBUF_WRITE macro with bounds check; replace all bare Avbuf[j++] writes with the macro; add size check on label-arg path - ops.c v_dc: flush Gen[] before each character's bytes to prevent overflow - symbols.c findsymbol/CreateSymbol: widen local symbol buf from +14 to +22 bytes and switch sprintf→snprintf (20-digit index + separator + NUL) - util.c small_alloc/small_free_all: promote static locals buf/left to file-scope (small_alloc_buf/small_alloc_left) so small_free_all() can reset them, preventing use-after-free on the next assembly pass Correctness - main.c asmerr(): move snprintf before fprintf to avoid passing a non-literal format string directly to fprintf (format-string injection) - main.c passbuffer_update(): change newsizerequired/sizes from int to size_t; fix OOM recovery path to return without corrupting state - ops.c v_align(): reject alignment value of zero (was silent divide-by-zero) - ops.c v_incbin(): fix free condition (buf!=fname not buf!=str); add FreeSymbolList(sym) to plug per-pass memory leak; reject negative skip - ops.c genfill(): guard word/long size multiplications against signed integer overflow UB before shifting - ops.c v_setsym(): replace strcpy with snprintf - exp.c op_shiftleft/op_shiftright(): cast to unsigned before left-shift to avoid UB; clamp out-of-range shift counts to zero - ftohex.c getwlh(): store getc() result in int and check for EOF before casting; open input file in binary mode ("rb") - ftobin.c getwlh(): same EOF fix as ftohex.c Style - Apply K&R formatting (.clang-format) across all source files - Add blank line after opening brace of each function bodyConfiguration menu - View commit details
-
Copy full SHA for afe99c0 - Browse repository at this point
Copy the full SHA afe99c0View commit details -
Configuration menu - View commit details
-
Copy full SHA for d2c027a - Browse repository at this point
Copy the full SHA d2c027aView commit details -
Configuration menu - View commit details
-
Copy full SHA for 221a236 - Browse repository at this point
Copy the full SHA 221a236View commit details -
Configuration menu - View commit details
-
Copy full SHA for 90922e4 - Browse repository at this point
Copy the full SHA 90922e4View commit details -
Configuration menu - View commit details
-
Copy full SHA for a727cc9 - Browse repository at this point
Copy the full SHA a727cc9View commit details -
fix: permalloc uses int instead of size_t
permalloc() declaration in asm.h and definition in main.c used int bytes; the internal left counter was also int. Changed both to size_t to match checked_malloc/zero_malloc. fix: retire ckmalloc/zmalloc in favour of checked_malloc/zero_malloc ckmalloc and zmalloc in main.c duplicated checked_malloc/zero_malloc from util.c with weaker error reporting and int parameters. Removed the old functions; all call sites in ops.c, exp.c, main.c, and mnef8.c updated. Added #include "util.h" to asm.h so the replacements are available everywhere. fix: sizeof(ISEGNAME) used where strlen+1 is needed permalloc(sizeof(ISEGNAME)) produced a buffer of 4 or 8 bytes (pointer size) instead of 21 (string length). Replaced with permalloc(strlen(ISEGNAME) + 1). This was a heap overflow on every run. fix: sprintf → snprintf in mnemonic error message buffers sBuffer[128] in v_mnemonic() and v_dc() in ops.c, and sBuffer[MAX_SYM_LEN*4] in symbols.c, used sprintf with operands up to MAXLINE (1024) bytes. Replaced all instances with snprintf(buf, sizeof(buf), ...). fix: stackarg() and doop() write to array before overflow check Both functions wrote to Argstack[Argi]/Opdis[Opi] and then checked whether the index had exceeded the array bound. Reordered to check first and return early, preventing potential out-of-bounds writes. fix: v_hex() calls gethexdig() on null byte for odd-length input When the HEX directive had an odd number of digits, gethexdig(str[i+1]) was called with '\0', producing a spurious "Bad Hex Digit" error. Now checks str[i+1] != '\0' first and emits a proper "odd number of hex digits" error. fix: clearrefs() loop variable was short, should be int SHASHSIZE can exceed SHORT_MAX; loop index changed from short to int. fix: bStopAtEnd[] read at end of pass loop lacks bounds guard Added a defensive bounds check on pass before reading bStopAtEnd[pass], parallel to the existing write guard in asmerr(). fix: errors.c print_error_message assert on empty string replaced with runtime guard assert(strlen(message) > 0) fires in release builds and kills the process. Replaced with a runtime if (strlen(message) == 0) return that survives without NDEBUG. fix: test_errors.c incompatible function pointer cast (void(*)(void))flush_deferred_errors is undefined behaviour — flush_deferred_errors takes a FILE *. Added _flush_deferred_errors_wrapper() helper and used that instead. fix: ftobin.c opens files in text mode fopen(av[2], "r") and fopen(av[3], "w") opened binary files in text mode, corrupting data on Windows (CR/LF translation, 0x1A EOF). Changed to "rb" and "wb". fix: unchecked ftell() calls in generate() Three ftell(FI_temp) calls stored results into long but were never checked for -1. Added error checks with asmerr(ERROR_FILE_ERROR, ...) on failure. Also added ferror() checks at the end of generate() and closegenerate() to catch any silent putc()/fwrite() failures. fix: mnef8.c BUG comments replaced with accurate notes Removed overstated "BUG:" labels from F8 backend. The parse_value() return-value pattern is intentional in DASM's multi-pass model; the branch displacement -1 has been in production since 2004 without reported issues. Comments now reflect what is actually known.
Configuration menu - View commit details
-
Copy full SHA for cee3220 - Browse repository at this point
Copy the full SHA cee3220View commit details
Commits on Jul 7, 2026
-
Merge branch 'master' into resolve_issue_with_carriage_return_breakin…
…g_character_literals
Configuration menu - View commit details
-
Copy full SHA for e3720e8 - Browse repository at this point
Copy the full SHA e3720e8View commit details -
Merge pull request #148 from ajxs/resolve_issue_with_carriage_return_…
…breaking_character_literals Resolve issue with carriage return breaking whitespace character literals
Configuration menu - View commit details
-
Copy full SHA for c361b82 - Browse repository at this point
Copy the full SHA c361b82View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff bugfix...master