Skip to content

Commit b73a11c

Browse files
authored
Merge pull request #126: fix ASAN leaks + OOB read + rotate-by-N UB
fix: ASAN-surfaced bugs (closes #125, #127) Six commits, each independently reviewable, addressing every issue the sanitizers job uncovered after PR #121 wired it up: - table68k + branch_condition_table leaked across dlopen/dlclose cycles (closes #125; symmetric m68k_done / GPUDone wired into JaguarDone, with free_table68k() in readcpu.c so ownership stays inside the module that allocates). - test_hle_bios.c was missing a final p_retro_unload_game() so JaguarDone() never ran for the PAL load -> the leak fix above didn't appear to take effect until that test was corrected. - 1-byte global-buffer-overflow read past tomRam8 in five tom_render_*_scanline() functions (closes #127); new tom_clamp_line_buffer_width() helper used in all five. - Rotate-by-zero UB in 6 ROR sites and rotate-by-32 UB in 3 RORQ sites across src/tom/gpu.c and src/jerry/dsp.c, fixed via the standard portable `(x >> r) | (x << ((-r) & 31))` idiom plus masking `r` to 0x1F when sourced from *_convert_zero[]. clang-tidy curated check list updated: - bugprone-incorrect-roundings (USEC_TO_*_CYCLES macro pattern) - bugprone-multi-level-implicit-pointer-conversion (dlsym idiom) - clang-analyzer-optin.performance.Padding (UAE struct layouts) CI: 30/30 green including the previously-advisory sanitizer job. Candidate to flip continue-on-error: true -> false on that job in a follow-up.
2 parents 709ffca + 3d3fde0 commit b73a11c

11 files changed

Lines changed: 104 additions & 11 deletions

File tree

.clang-tidy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ Checks: >
2424
-bugprone-suspicious-include,
2525
-bugprone-switch-missing-default-case,
2626
-bugprone-branch-clone,
27+
-bugprone-incorrect-roundings,
28+
-bugprone-multi-level-implicit-pointer-conversion,
2729
-clang-analyzer-deadcode.DeadStores,
30+
-clang-analyzer-optin.performance.Padding,
2831
-clang-analyzer-optin.portability.UnixAPI,
2932
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
3033
-clang-analyzer-valist.Uninitialized
@@ -49,6 +52,13 @@ FormatStyle: none
4952
# - suspicious-include: project includes .c files in a couple of dispatch headers.
5053
# - switch-missing-default-case: cosmetic; switches on bit-field decode patterns
5154
# commonly omit default because all valid bit values are handled.
55+
# - incorrect-roundings: src/core/event.h's USEC_TO_RISC_CYCLES /
56+
# USEC_TO_M68K_CYCLES use the (double + 0.5) integer-cast idiom for
57+
# cycle conversion. lround() is C99 (we're C89/GNU89), and the
58+
# inputs are non-negative so the idiom is correct here.
59+
# - multi-level-implicit-pointer-conversion: dlsym() returns void*
60+
# that test harnesses cast directly to function pointers / data
61+
# pointer-to-pointer; canonical idiom in the libretro dlsym pattern.
5262
# - branch-clone: register-decode if-chains in src/cd/cdrom.c and src/tom/tom.c
5363
# intentionally write the same value for several adjacent register addresses
5464
# to make the address->effect mapping legible. Real bug clones are caught
@@ -57,5 +67,8 @@ FormatStyle: none
5767
# locals that are read via macros (BCOMPEN, DSTA2, ...) clang-tidy can't see
5868
# the linkage for. Removing these inits would introduce real bugs.
5969
# - DeprecatedOrUnsafeBufferHandling: MSVC-flavored noise about strcpy/sprintf.
70+
# - optin.performance.Padding: upstream UAE/Virtual Jaguar struct layouts
71+
# inherited from 1990s emulator code; reordering for tighter packing risks
72+
# silent layout breaks in save-state / dlsym paths and isn't free perf.
6073
# - optin.portability.UnixAPI: false positive on libretro_core_options.h calloc.
6174
# - valist.Uninitialized: false-positive prone on our log macros.

src/core/jaguar.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,7 @@ void JaguarDone(void)
919919
DSPDone();
920920
TOMDone();
921921
JERRYDone();
922+
m68k_done();
922923
}
923924

924925
uint8_t * GetRamPtr(void)

src/jerry/dsp.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,17 +1409,20 @@ INLINE static void dsp_opcode_shrq(void)
14091409
INLINE static void dsp_opcode_ror(void)
14101410
{
14111411
uint32_t r1 = RM & 0x1F;
1412-
uint32_t res = (RN >> r1) | (RN << (32 - r1));
1412+
uint32_t res = (RN >> r1) | (RN << ((-r1) & 31));
14131413
SET_ZN(res); dsp_flag_c = (RN >> 31) & 1;
14141414
RN = res;
14151415
}
14161416

14171417

14181418
INLINE static void dsp_opcode_rorq(void)
14191419
{
1420-
uint32_t r1 = dsp_convert_zero[IMM_1 & 0x1F];
1420+
/* dsp_convert_zero[0] returns 32 (rotate-by-0 means rotate-by-full-word,
1421+
* a no-op). Masking to 0x1F maps 32 -> 0, preserving that semantic and
1422+
* avoiding `RN >> 32` UB in the rotate idiom below. */
1423+
uint32_t r1 = dsp_convert_zero[IMM_1 & 0x1F] & 0x1F;
14211424
uint32_t r2 = RN;
1422-
uint32_t res = (r2 >> r1) | (r2 << (32 - r1));
1425+
uint32_t res = (r2 >> r1) | (r2 << ((-r1) & 31));
14231426
RN = res;
14241427
SET_ZN(res); dsp_flag_c = (r2 >> 31) & 0x01;
14251428
}
@@ -2226,16 +2229,17 @@ INLINE static void DSP_resmac(void)
22262229
INLINE static void DSP_ror(void)
22272230
{
22282231
uint32_t r1 = PRM & 0x1F;
2229-
uint32_t res = (PRN >> r1) | (PRN << (32 - r1));
2232+
uint32_t res = (PRN >> r1) | (PRN << ((-r1) & 31));
22302233
SET_ZN(res); dsp_flag_c = (PRN >> 31) & 1;
22312234
PRES = res;
22322235
}
22332236

22342237
INLINE static void DSP_rorq(void)
22352238
{
2236-
uint32_t r1 = dsp_convert_zero[PIMM1 & 0x1F];
2239+
/* See dsp_opcode_rorq above for why we mask to 0x1F. */
2240+
uint32_t r1 = dsp_convert_zero[PIMM1 & 0x1F] & 0x1F;
22372241
uint32_t r2 = PRN;
2238-
uint32_t res = (r2 >> r1) | (r2 << (32 - r1));
2242+
uint32_t res = (r2 >> r1) | (r2 << ((-r1) & 31));
22392243
PRES = res;
22402244
SET_ZN(res); dsp_flag_c = (r2 >> 31) & 0x01;
22412245
}

src/m68000/m68kinterface.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,23 @@ void M68KDebugResume(void)
7878
}
7979

8080

81+
/* File-scope so m68k_done() below can reset it after freeing table68k. */
82+
static uint32_t emulation_initialized = 0;
83+
84+
/* Symmetric counterpart to the lazy init in m68k_pulse_reset().
85+
* Defers the actual free to readcpu.c::free_table68k() so the table
86+
* is owned end-to-end by the module that allocates it (table68k is
87+
* declared in readcpu.h). Called from JaguarDone() so ASAN sees a
88+
* clean shutdown. */
89+
void m68k_done(void)
90+
{
91+
free_table68k();
92+
emulation_initialized = 0;
93+
}
94+
8195
// Pulse the RESET line on the CPU
8296
void m68k_pulse_reset(void)
8397
{
84-
static uint32_t emulation_initialized = 0;
85-
8698
// The first call to this function initializes the opcode handler jump table
8799
if (!emulation_initialized)
88100
{

src/m68000/m68kinterface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ typedef enum
7070
#define M68K_INT_ACK_SPURIOUS 0xFFFFFFFE
7171

7272
void m68k_pulse_reset(void);
73+
void m68k_done(void);
7374
int m68k_execute(int num_cycles);
7475
void m68k_set_irq(unsigned int int_level);
7576

src/m68000/readcpu.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,20 @@ static void build_insn(int insn)
922922
}
923923

924924

925+
/* Symmetric counterpart to read_table68k: free the opcode table built
926+
above. Idempotent (free(NULL) is a no-op). Called from
927+
m68k_done() in m68kinterface.c so process-lifetime ASAN runs see a
928+
clean shutdown. */
929+
void free_table68k(void)
930+
{
931+
if (table68k)
932+
{
933+
free(table68k);
934+
table68k = NULL;
935+
}
936+
}
937+
938+
925939
void read_table68k(void)
926940
{
927941
int i;

src/m68000/readcpu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ extern struct instr {
112112
} *table68k;
113113

114114
extern void read_table68k(void);
115+
extern void free_table68k(void);
115116
extern void do_merges(void);
116117
extern int get_no_mismatches(void);
117118
extern int nr_cpuop_funcs;

src/tom/gpu.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,16 @@ void GPUInit(void)
642642
GPUReset();
643643
}
644644

645+
void GPUDone(void)
646+
{
647+
/* Release the branch-condition LUT so process-lifetime ASAN runs
648+
* don't report it as a leak. Unconditional: free(NULL) is a
649+
* no-op, and a subsequent GPUInit() re-allocates cleanly because
650+
* build_branch_condition_table() early-outs on non-NULL pointer. */
651+
free(branch_condition_table);
652+
branch_condition_table = NULL;
653+
}
654+
645655
void GPUReset(void)
646656
{
647657
unsigned i;
@@ -1661,17 +1671,20 @@ INLINE static void gpu_opcode_shrq(void)
16611671
INLINE static void gpu_opcode_ror(void)
16621672
{
16631673
uint32_t r1 = RM & 0x1F;
1664-
uint32_t res = (RN >> r1) | (RN << (32 - r1));
1674+
uint32_t res = (RN >> r1) | (RN << ((-r1) & 31));
16651675
SET_ZN(res); gpu_flag_c = (RN >> 31) & 1;
16661676
RN = res;
16671677
}
16681678

16691679

16701680
INLINE static void gpu_opcode_rorq(void)
16711681
{
1672-
uint32_t r1 = gpu_convert_zero[IMM_1 & 0x1F];
1682+
/* gpu_convert_zero[0] returns 32 (rotate-by-0 means rotate-by-full-word
1683+
* which is a no-op). Masking to 0x1F maps 32 -> 0, preserving that
1684+
* semantic and avoiding `RN >> 32` UB in the rotate idiom below. */
1685+
uint32_t r1 = gpu_convert_zero[IMM_1 & 0x1F] & 0x1F;
16731686
uint32_t r2 = RN;
1674-
uint32_t res = (r2 >> r1) | (r2 << (32 - r1));
1687+
uint32_t res = (r2 >> r1) | (r2 << ((-r1) & 31));
16751688
RN = res;
16761689
SET_ZN(res); gpu_flag_c = (r2 >> 31) & 0x01;
16771690
}

src/tom/gpu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern "C" {
1515
#define GPU_WORK_RAM_BASE 0x00F03000
1616

1717
void GPUInit(void);
18+
void GPUDone(void);
1819
void GPUReset(void);
1920
void GPUExec(int32_t);
2021
void GPUUpdateRegisterBanks(void);

src/tom/tom.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,29 @@ uint16_t TOMGetMEMCON1(void)
548548
}
549549

550550
#define LEFT_BG_FIX
551+
552+
/* Clamp `width` so the per-pixel loop in a tom_render_*_scanline()
553+
* cannot walk past the end of tomRam8 via current_line_buffer.
554+
* Catches an ASAN-reported OOB read (#127): when display registers
555+
* request a width larger than the on-chip line buffer holds
556+
* (10240 bytes at tomRam8[0x1800..0x3FFF]).
557+
*
558+
* bytes_per_iter: source bytes consumed per loop iter (2 for 16bpp
559+
* variants, 4 for 24bpp).
560+
* pwidth_scale: backbuffer pixels produced per loop iter. */
561+
static uint16_t tom_clamp_line_buffer_width(
562+
const uint8_t *current_line_buffer, uint16_t width,
563+
unsigned bytes_per_iter, uint8_t pwidth_scale)
564+
{
565+
const uint8_t *lb_end = &tomRam8[sizeof(tomRam8)];
566+
unsigned long bytes_left;
567+
unsigned long safe_width;
568+
if (current_line_buffer >= lb_end) return 0;
569+
bytes_left = (unsigned long)(lb_end - current_line_buffer);
570+
safe_width = (bytes_left / bytes_per_iter) * pwidth_scale;
571+
return (width > safe_width) ? (uint16_t)safe_width : width;
572+
}
573+
551574
// 16 BPP CRY/RGB mixed mode rendering
552575
void tom_render_16bpp_cry_rgb_mix_scanline(uint32_t * backbuffer)
553576
{
@@ -579,6 +602,7 @@ void tom_render_16bpp_cry_rgb_mix_scanline(uint32_t * backbuffer)
579602
backbuffer += 2 * startPos, width -= startPos;
580603
#endif
581604

605+
width = tom_clamp_line_buffer_width(current_line_buffer, width, 2, pwidth_scale);
582606
while (width >= pwidth_scale)
583607
{
584608
uint16_t color = (*current_line_buffer++) << 8;
@@ -620,6 +644,7 @@ void tom_render_16bpp_cry_scanline(uint32_t * backbuffer)
620644
backbuffer += 2 * startPos, width -= startPos;
621645
#endif
622646

647+
width = tom_clamp_line_buffer_width(current_line_buffer, width, 2, pwidth_scale);
623648
while (width >= pwidth_scale)
624649
{
625650
uint16_t color = (*current_line_buffer++) << 8;
@@ -661,6 +686,7 @@ void tom_render_24bpp_scanline(uint32_t * backbuffer)
661686
backbuffer += 2 * startPos, width -= startPos;
662687
#endif
663688

689+
width = tom_clamp_line_buffer_width(current_line_buffer, width, 4, pwidth_scale);
664690
while (width >= pwidth_scale)
665691
{
666692
uint32_t b;
@@ -687,6 +713,7 @@ void tom_render_16bpp_direct_scanline(uint32_t * backbuffer)
687713
uint8_t pwidth = ((GET16(tomRam8, VMODE) & PWIDTH) >> 9) + 1;
688714
uint8_t pwidth_scale = (pwidth >= 8) ? (pwidth / 4) : 1;
689715

716+
width = tom_clamp_line_buffer_width(current_line_buffer, width, 2, pwidth_scale);
690717
while (width >= pwidth_scale)
691718
{
692719
uint16_t color = (*current_line_buffer++) << 8;
@@ -729,6 +756,7 @@ void tom_render_16bpp_rgb_scanline(uint32_t * backbuffer)
729756
backbuffer += 2 * startPos, width -= startPos;
730757
#endif
731758

759+
width = tom_clamp_line_buffer_width(current_line_buffer, width, 2, pwidth_scale);
732760
while (width >= pwidth_scale)
733761
{
734762
uint32_t color = (*current_line_buffer++) << 8;
@@ -846,6 +874,7 @@ void TOMDone(void)
846874
{
847875
OPDone();
848876
BlitterDone();
877+
GPUDone();
849878
}
850879

851880

0 commit comments

Comments
 (0)