Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Common/headers/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ typedef struct odb_context_guard {
void *saved_hashtablestack; /* hdltablestack */
void *saved_rootvariable; /* Handle */
void *saved_roottable; /* hdlhashtable */
void *saved_cancoonglobals; /* hdlcancoonrecord */
} odb_context_guard;

extern void odb_guard_enter(odb_context_guard *guard);
Expand Down
20 changes: 20 additions & 0 deletions Common/headers/odbinternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@

typedef struct odb_ * odbref;

/* ODB list record — tracks open guest databases (in-memory only, not a disk format).
* MUST use pack(2) to match legacy Frontier struct layout. Without pack(2), natural
* alignment shifts the odb field by 6 bytes, causing corrupted handle dereferences
* when db.setvalue/getvalue follow fileMenu.open in the same script.
* Shared between dbverbs.c and headless_filemenu_verbs.c. */
#pragma pack(2)
typedef struct tyodblistrecord {
struct tyodblistrecord **hnext;
tyfilespec fs;
hdlfilenum fref;
boolean flreadonly;
odbref odb;
} tyodbrecord, *ptrodbrecord, **hdlodbrecord;
#pragma options align=reset

_Static_assert(sizeof(tyodbrecord) == 614,
"tyodbrecord size changed — pack(2) layout must match dbverbs.c expectations");

typedef enum odbValueType {

unknownT = '\?\?\?\?',
Expand Down Expand Up @@ -171,6 +189,8 @@ extern pascal boolean odbOpenFile (hdlfilenum, odbref *odb, boolean flreadonly);

extern pascal boolean odbSaveFile (odbref odb);

extern pascal Handle odbGetRootVariable (odbref odb);

extern pascal boolean odbCloseFile (odbref odb);

extern pascal boolean odbDefined (odbref odb, bigstring bspath);
Expand Down
15 changes: 13 additions & 2 deletions Common/source/db_format.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,19 @@ void odb_guard_enter(odb_context_guard *guard) {
guard->saved_hashtablestack = (void *) hashtablestack;
guard->saved_rootvariable = (void *) rootvariable;
guard->saved_roottable = (void *) roottable;
guard->saved_cancoonglobals = (void *) cancoonglobals;

/* Nil out currenthashtable to prevent tmpstack contamination.
* Without this, copyvaluerecord during migration pushes handles onto
* the caller's local scope tmpstack via pushtmpstackvalue. When those
* handles are later freed by the guest database, cleartmpstack tries
* to double-free them — heap corruption. Setting nil makes
* pushtmpstackvalue return early (langtmpstack.c line 103). */
currenthashtable = nil;

#if defined(FRONTIER_HEADLESS)
log_trace(LOG_COMP_DB, "odb_guard_enter: saved db=%p root=%p currenttable=%p",
(void *) databasedata, (void *) rootvariable, (void *) currenthashtable);
log_trace(LOG_COMP_DB, "odb_guard_enter: saved db=%p root=%p currenttable=%p (now nil)",
(void *) databasedata, (void *) rootvariable, (void *) guard->saved_currenthashtable);
#endif
}

Expand All @@ -183,6 +193,7 @@ void odb_guard_exit(odb_context_guard *guard) {
hashtablestack = (hdltablestack) guard->saved_hashtablestack;
rootvariable = (Handle) guard->saved_rootvariable;
roottable = (hdlhashtable) guard->saved_roottable;
cancoonglobals = (hdlcancoonrecord) guard->saved_cancoonglobals;
}

#if defined(_WIN32)
Expand Down
36 changes: 17 additions & 19 deletions Common/source/dbverbs.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#include "processinternal.h"
#include "odbinternal.h"
#include "db_format.h" /* migration helpers */
#include "db.h" /* odb_context_guard */

/* Path buffer size - macOS typically supports up to 1024 byte paths */
#ifndef DB_PATH_MAX
Expand Down Expand Up @@ -365,26 +366,11 @@ swapping, and doesn't require thread infrastructure initialization.

#endif

#pragma pack(2)
typedef struct tyodblistrecord {

struct tyodblistrecord **hnext;

tyfilespec fs;

hdlfilenum fref;

boolean flreadonly;

odbref odb;

} tyodbrecord, *ptrodbrecord, **hdlodbrecord;
#pragma options align=reset

/* Global ODB list - used by both GUI and headless dbinitverbs()
* Uses sentinel pattern to prevent UAF when closing last database.
* The sentinel is a permanent allocated handle that's never freed,
* preventing hodblist from becoming a dangling pointer. */
* preventing hodblist from becoming a dangling pointer.
* tyodbrecord/hdlodbrecord defined in odbinternal.h */
hdlodbrecord hodblist = nil; /* Initialized to sentinel handle on first use */


Expand Down Expand Up @@ -724,9 +710,21 @@ static boolean dbnewverb (hdltreenode hparam1, tyvaluerecord *vreturned) {
if (!fl)
return (false);

fl = odbnewfile (odbrec.fref);
{
/*
* 2026-02-06: odb_context_guard protects databasedata, rootvariable,
* roottable, currenthashtable, and hashtablestack from being stomped
* by dbnew() / dbdispose() inside odbnewfile().
*/
odb_context_guard guard;
odb_guard_enter (&guard);

fl = odbnewfile (odbrec.fref);

closefile (odbrec.fref);
closefile (odbrec.fref);

odb_guard_exit (&guard);
}

if (odberror (fl)) {

Expand Down
10 changes: 8 additions & 2 deletions Common/source/langexternal.c
Original file line number Diff line number Diff line change
Expand Up @@ -1022,8 +1022,14 @@ boolean langexternalpack_internal (const db_context *ctx, hdlexternalhandle h, H
log_trace(LOG_COMP_EXTERNAL, "langexternalpack: using v7 write context flinmemory=%d (no global mode set)",
(int)(**hv).flinmemory);
} else {
log_debug(LOG_COMP_EXTERNAL, "langexternalpack_internal: NOT adapter_repack - skipping load! id=%d flinmemory=%d",
(int)(**hv).id, (int)(**hv).flinmemory);
/* Normal save: load from current database if not already in memory */
if (!(**hv).flinmemory) {
if (!ensure_external_in_memory (&working_context, hv)) {
log_error(LOG_COMP_EXTERNAL, "langexternalpack_internal: ensure_external_in_memory FAILED (normal save) id=%d",
(int)(**hv).id);
return (false);
}
}
}

/* ================================================================
Expand Down
145 changes: 108 additions & 37 deletions Common/source/menupack.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,41 +352,127 @@ static boolean mepackscriptvisit (hdlheadrecord hnode, ptrvoid refcon) {
} /*mepackscriptvisit*/


static boolean mesavemenustructure (tysavedmenuinfo *info, dbaddress *adr) {

typedef struct tysavedmenuinfo_v7 {
uint16_t versionnumber; /* 2 bytes */
uint8_t _pad0[6]; /* 6 bytes padding to 8-byte boundary */
uint64_t adroutline; /* 8 bytes, big-endian on disk */
int64_t lnumcursor; /* 8 bytes, big-endian on disk */
uint32_t flags; /* 4 bytes, big-endian on disk */
uint32_t menuactiveitem; /* 4 bytes, big-endian on disk */
uint8_t _reserved[1024]; /* 1024 bytes reserved for future use */
} tysavedmenuinfo_v7;

_Static_assert(sizeof(tysavedmenuinfo_v7) == 1056, "v7 menu struct must be exactly 1056 bytes");


static boolean mesavemenustructure_legacy (hdlmenurecord hm, dbaddress *adr) {

/*
everything has already been set up: everything pushed and dehoisted.
save all of the data associated with the menubar, by visiting every
node in the menu structure, saving off all linked scripts, and then
Legacy (v6) save path: save the menu structure with 32-bit BE addresses.

everything has already been set up: everything pushed and dehoisted.
save all of the data associated with the menubar, by visiting every
node in the menu structure, saving off all linked scripts, and then
saving the menubar outline itself

after a script is saved, we dispose of the in-memory structure, unless it
is the active script, or we're doing a Save As.
*/

hdlheadrecord hsummit;
register boolean fl;

tysavedmenuinfo info;

opoutermostsummit (&hsummit);

if (fldatabasesaveas)
fl = opsiblingvisiter (hsummit, false, &mesaveasscriptvisit, nil);
else
fl = opsiblingvisiter (hsummit, false, &mesavescriptvisit, nil);

assert (opvalidate (op_get_outlinedata()));

if (!fl)
return (false);

disktomemlong ((*info).adroutline);

if (!mesaveoutline (op_get_outlinedata(), &(*info).adroutline))
dbaddress new_outline_adr;

clearbytes (&info, sizeof (info));

info.versionnumber = conditionalshortswap (1);

info.adroutline = (**hm).adroutline;

if (!mesaveoutline (op_get_outlinedata(), &info.adroutline))
return (false);

db_format_write_be32(&(*info).adroutline, (uint32_t) (*info).adroutline);

return (dbassign (adr, sizeof (tysavedmenuinfo), info));

/* Capture the updated host-order address before BE32 conversion */
new_outline_adr = info.adroutline;

db_format_write_be32(&info.adroutline, (uint32_t) info.adroutline);

fl = dbassign (adr, sizeof (tysavedmenuinfo), &info);

/* Update in-memory address with new outline address */
if (fl)
(**hm).adroutline = new_outline_adr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Save As check corrupts menu address after save

Medium Severity

During refactoring, the !fldatabasesaveas check was removed from both mesavemenustructure_legacy and mesavemenustructure_v7. The old code only updated (**hm).adroutline when NOT doing a Save As operation, but the new code unconditionally updates it. After a Save As, subsequent regular saves will now go to the Save As destination instead of the original file, breaking expected Save As semantics where the original document remains unchanged.

Additional Locations (1)

Fix in Cursor Fix in Web


return (fl);
} /*mesavemenustructure_legacy*/


static boolean mesavemenustructure_v7 (hdlmenurecord hm, dbaddress *adr) {

/*
V7 save path: save the menu structure with 64-bit BE addresses.
Uses tysavedmenuinfo_v7 disk format.
*/

hdlheadrecord hsummit;
register boolean fl;
tysavedmenuinfo_v7 v7info;
dbaddress outline_adr;

opoutermostsummit (&hsummit);

if (fldatabasesaveas)
fl = opsiblingvisiter (hsummit, false, &mesaveasscriptvisit, nil);
else
fl = opsiblingvisiter (hsummit, false, &mesavescriptvisit, nil);

assert (opvalidate (op_get_outlinedata()));

if (!fl)
return (false);

outline_adr = (**hm).adroutline;

if (!mesaveoutline (op_get_outlinedata(), &outline_adr))
return (false);

clearbytes (&v7info, sizeof (v7info));

v7info.versionnumber = host_to_disk_uint16 (2);
v7info.adroutline = host_to_disk_uint64 ((uint64_t) outline_adr);
v7info.lnumcursor = host_to_disk_uint64 (0); /* cursor position saved with outline */
v7info.flags = host_to_disk_uint32 ((uint32_t) ((**hm).flautosmash ? flautosmash_mask : 0));
v7info.menuactiveitem = host_to_disk_uint32 ((uint32_t) (**hm).menuactiveitem);

fl = dbassign (adr, sizeof (tysavedmenuinfo_v7), &v7info);

if (fl)
(**hm).adroutline = outline_adr;

return (fl);
} /*mesavemenustructure_v7*/


static boolean mesavemenustructure (hdlmenurecord hm, dbaddress *adr) {

if (db_format_mode_current().use_64bit_format)
return mesavemenustructure_v7 (hm, adr);
else
return mesavemenustructure_legacy (hm, adr);
} /*mesavemenustructure*/


Expand Down Expand Up @@ -467,7 +553,7 @@ boolean mesavemenurecord (hdlmenurecord hmenurecord, boolean flpreservelinks, bo

info.versionnumber = conditionalshortswap (1);

info.adroutline = conditionallongswap ((**hm).adroutline);
info.adroutline = (**hm).adroutline;

info.vertmin = conditionalshortswap ((**ho).vertscrollinfo.min);

Expand Down Expand Up @@ -549,10 +635,7 @@ boolean mesavemenurecord (hdlmenurecord hmenurecord, boolean flpreservelinks, bo
fl = mepackmenustructure (&info, hpacked);
}
else {
fl = mesavemenustructure (&info, adr);

if (fl && (!fldatabasesaveas))
(**hm).adroutline = conditionallongswap(info.adroutline);
fl = mesavemenustructure (hm, adr);
}
}

Expand Down Expand Up @@ -605,7 +688,7 @@ boolean mesetupmenurecord (tysavedmenuinfo *info, hdloutlinerecord houtline, hdl

(**ho).outlinerefcon = (long) hm; /*pointing is mutual*/

(**hm).adroutline = conditionallongswap((*lpi).adroutline); /*keep address around for save*/
(**hm).adroutline = (*lpi).adroutline; /*keep address around for save*/

diskrecttorect (&(*lpi).scriptwindowrect, &(**hm).scriptwindowrect);

Expand Down Expand Up @@ -749,18 +832,6 @@ typedef struct tysavedmenuinfo_disk_legacy {
/* Verify the legacy struct size matches v6 format expectations (112 bytes) */
_Static_assert(sizeof(tysavedmenuinfo_disk_legacy) == 112, "v6 menu struct must be exactly 112 bytes");

typedef struct tysavedmenuinfo_v7 {
uint16_t versionnumber; /* v7+ marker */
uint16_t _pad; /* align to 64-bit */
uint64_t adroutline; /* BE64 address of menubar outline */
uint64_t lnumcursor; /* BE64 line number of current selection */
uint32_t flags; /* preserve autosmash/menuactive flags (expanded) */
uint32_t menuactiveitem; /* active item enum */
uint8_t reserved[1024]; /* 1KB future padding */
} tysavedmenuinfo_v7;

_Static_assert(sizeof(tysavedmenuinfo_v7) == 1056, "v7 menu struct must be exactly 1056 bytes");

static boolean mepackmenustructure_v7(tysavedmenuinfo *legacy, Handle *hpacked) {
tysavedmenuinfo_v7 modern;
clearbytes(&modern, sizeof(modern));
Expand All @@ -769,7 +840,7 @@ static boolean mepackmenustructure_v7(tysavedmenuinfo *legacy, Handle *hpacked)
modern.adroutline = host_to_disk_uint64((uint64_t) legacy->adroutline);
modern.lnumcursor = host_to_disk_uint64((uint64_t) legacy->lnumcursor);
modern.flags = host_to_disk_uint32((uint32_t) legacy->flags);
modern.menuactiveitem = host_to_disk_uint32((uint32_t) legacy->menuactivelayer);
modern.menuactiveitem = host_to_disk_uint32((uint32_t) legacy->menuactivelayer); /* menuactivelayer in tysavedmenuinfo maps to menuactiveitem in v7 */

Handle hpackedmenu = nil;
Handle hpackedoutline = nil;
Expand Down
11 changes: 11 additions & 0 deletions Common/source/odbengine.c
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,17 @@ pascal boolean odbOpenFile (hdlfilenum fnum, odbref *odb, boolean flreadonly) {
} /*odbOpenFile*/


pascal Handle odbGetRootVariable (odbref odb) {

if (odb == nil)
return (nil);

hdlcancoonrecord hc = (hdlcancoonrecord) odb;

return ((Handle) (**hc).hrootvariable);
} /*odbGetRootVariable*/
Comment thread
cursor[bot] marked this conversation as resolved.


pascal boolean odbSaveFile (odbref odb) {

hdlcancoonrecord hc = (hdlcancoonrecord) odb;
Expand Down
Loading