2 Copyright © 1995-2015, The AROS Development Team. All rights reserved.
5 Desc: MungWall memory anti-trashing checker
9 #include <exec/alerts.h>
10 #include <exec/rawfmt.h>
11 #include <proto/exec.h>
14 #include "exec_intern.h"
15 #include "exec_util.h"
22 * Build a wall around the allocated chunk.
23 * Returns updated pointer to the beginning of the chunk (actually a pointer to a usable area)
25 * 'res' is a pointer to the beginning of a raw memory block (inside which walls will be constructed)
26 * 'origSize' is ORIGINAL allocation size (before adding mungwall size)
28 APTR
MungWall_Build(APTR res
, APTR pool
, IPTR origSize
, ULONG requirements
, struct TraceLocation
*loc
, struct ExecBase
*SysBase
)
30 if ((PrivExecBase(SysBase
)->IntFlags
& EXECF_MungWall
) && res
)
32 struct MungwallHeader
*header
= res
;
34 D(bug("[MungWall] Allocated %u bytes at 0x%p\n", origSize
, res
+ MUNGWALL_BLOCK_SHIFT
);)
37 * Build pre-wall starting from header, and up to MUNGWALL_BLOCK_SHIFT.
38 * This will fill also header padding. Will help in detecting issues.
40 memset(res
, 0xDB, MUNGWALL_BLOCK_SHIFT
);
43 * Now save allocation info in the header (there is one room of MUNGWALLHEADER_SIZE
44 * bytes before wall for such stuff (see above).
46 header
->mwh_magicid
= MUNGWALL_HEADER_ID
;
47 header
->mwh_fault
= FALSE
;
48 header
->mwh_allocsize
= origSize
;
49 header
->mwh_pool
= pool
;
50 header
->mwh_AllocFunc
= loc
->function
;
51 header
->mwh_Owner
= GET_THIS_TASK
;
52 header
->mwh_Caller
= loc
->caller
;
54 /* Skip to the start of data space */
55 res
+= MUNGWALL_BLOCK_SHIFT
;
57 /* Fill the block with weird stuff to exploit bugs in applications */
58 if (!(requirements
& MEMF_CLEAR
))
59 MUNGE_BLOCK(res
, MEMFILL_ALLOC
, origSize
);
62 * Initialize post-wall.
63 * Fill only MUNGWALL_SIZE bytes, this is what we are guaranteed to have in the end.
64 * We can't assume anything about padding. AllocMem() does guarantee that the actual
65 * allocation start and size is a multiple of MEMCHUNK_TOTAL, but for example
66 * AllocPooled() doesn't. Pooled allocations are only IPTR-aligned (see InternalFreePooled(),
67 * in AROS pooled allocations are vectored, pool pointer is stored in an IPTR preceding the
68 * actual block in AllocVec()-alike manner).
69 * IPTR alignment is 100% correct since original AmigaOS(tm) autodocs say that even AllocMem()
70 * result is longword-aligned. AROS introduces additional assumption that AllocMem() result
71 * is aligned at least up to AROS_WORSTALIGN (CPU-specific value, see exec/memory.h), however
72 * this is still not true for other allocation functions (AllocVec() etc).
74 memset(res
+ origSize
, 0xDB, MUNGWALL_SIZE
);
77 AddHead((struct List
*)&PrivExecBase(SysBase
)->AllocMemList
, (struct Node
*)&header
->mwh_node
);
83 char *FormatMWContext(char *buffer
, struct MungwallContext
*ctx
, struct ExecBase
*SysBase
)
85 buffer
= NewRawDoFmt("In %s, block at 0x%p, size %lu", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
, ctx
->freeFunc
, (APTR
)ctx
->hdr
+ MUNGWALL_BLOCK_SHIFT
, ctx
->hdr
->mwh_allocsize
) - 1;
86 buffer
= NewRawDoFmt("\nAllocated using %s ", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
, ctx
->hdr
->mwh_AllocFunc
) - 1;
87 buffer
= FormatTask(buffer
, "by task 0x%p (%s)", ctx
->hdr
->mwh_Owner
, SysBase
);
88 buffer
= FormatLocation(buffer
, " at 0x%p", ctx
->hdr
->mwh_Caller
, SysBase
);
90 if (ctx
->hdr
->mwh_magicid
!= MUNGWALL_HEADER_ID
)
91 buffer
= NewRawDoFmt("\nMUNGWALL_HEADER_ID mismatch", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
) - 1;
94 buffer
= NewRawDoFmt("\nFreeMem size %lu mismatch", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
, ctx
->freeSize
) - 1;
97 buffer
= NewRawDoFmt("\nPre-wall broken at 0x%p - 0x%p", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
, ctx
->pre_start
, ctx
->pre_end
) - 1;
100 buffer
= NewRawDoFmt("\nPost-wall broken at 0x%p - 0x%p", (VOID_FUNC
)RAWFMTFUNC_STRING
, buffer
, ctx
->post_start
, ctx
->post_end
) - 1;
105 static APTR
CheckWall(UBYTE
*ptr
, UBYTE fill
, IPTR size
, APTR
*endptr
)
127 static void CheckHeader(struct MungwallHeader
*header
, IPTR byteSize
, struct TraceLocation
*loc
, struct ExecBase
*SysBase
)
129 struct MungwallContext mwdata
;
131 /* Do not report the fault twice on the same header */
132 if (header
->mwh_fault
)
135 if (header
->mwh_magicid
!= MUNGWALL_HEADER_ID
)
136 header
->mwh_fault
= TRUE
;
138 if (byteSize
&& (header
->mwh_allocsize
!= byteSize
))
140 mwdata
.freeSize
= byteSize
;
141 header
->mwh_fault
= TRUE
;
146 mwdata
.pre_start
= CheckWall((UBYTE
*)header
+ MUNGWALLHEADER_SIZE
, 0xDB, MUNGWALL_SIZE
, &mwdata
.pre_end
);
147 if (mwdata
.pre_start
)
148 header
->mwh_fault
= TRUE
;
150 mwdata
.post_start
= CheckWall((UBYTE
*)header
+ MUNGWALL_BLOCK_SHIFT
+ header
->mwh_allocsize
, 0xDB, MUNGWALL_SIZE
, &mwdata
.post_end
);
151 if (mwdata
.post_start
)
152 header
->mwh_fault
= TRUE
;
154 if (header
->mwh_fault
)
156 /* Throw an alert with context */
158 mwdata
.freeFunc
= loc
->function
;
159 Exec_ExtAlert(AN_MemoryInsane
, loc
->caller
, loc
->stack
, AT_MUNGWALL
, &mwdata
, SysBase
);
162 * Our entry can be freed by another process while we are sitting in Alert().
163 * This is 100% safe as long as we don't touch the entry after Alert().
164 * Well, potentally dangerous case is list iteration in MungWall_Scan().
165 * What to do then? Use a semaphore? Won't do because of RemTask(NULL),
166 * during which SysBase->ThisTask becomes garbage, thus a semaphore can't
173 * Check integrity of walls around the specified block.
175 * 'memoryBlock' is an address of the block from user's point of view
176 * (i. e. what was returned by allocation call)
177 * 'byteSize' is length of the block (again, from user's point of view(
179 * Returns address of the raw block (what really needs to be deallocated)
181 APTR
MungWall_Check(APTR memoryBlock
, IPTR byteSize
, struct TraceLocation
*loc
, struct ExecBase
*SysBase
)
183 if (PrivExecBase(SysBase
)->IntFlags
& EXECF_MungWall
)
185 struct MungwallHeader
*header
;
187 D(bug("[MungWall] Freeing %u bytes at 0x%p\n", byteSize
, memoryBlock
);)
189 /* Align size and block to the requirements (needed because of AllocAbs) */
190 byteSize
+= (IPTR
)memoryBlock
& (MEMCHUNK_TOTAL
- 1);
191 memoryBlock
= (APTR
)AROS_ROUNDDOWN2((IPTR
)memoryBlock
, MEMCHUNK_TOTAL
);
193 /* Take address of mungwall header */
194 header
= memoryBlock
- MUNGWALL_BLOCK_SHIFT
;
197 * Remove from PrivExecBase->AllocMemList.
198 * Do it before checking, otherwise AvailMem() can hit into it and cause a deadlock
199 * while the alert is displayed.
202 Remove((struct Node
*)header
);
205 /* Reset fault state in order to see who is freeing the bad entry */
206 header
->mwh_fault
= FALSE
;
208 CheckHeader(header
, byteSize
, loc
, SysBase
);
210 /* Fill block with weird stuff to esploit bugs in applications
212 * DOH! There's some _BAD_ code around that assumes memory can still be
213 * accessed after freeing by just preventing task switching. In AROS,
214 * RemTask(NULL) suffers of this problem because DOS processes are
215 * created with their TCB placed in the tc_MemEntry list.
216 * The workaround is to avoid munging when current task is in TS_REMOVED
217 * state (RemTask() sets it). However RemTask() still needs reengineering
218 * before memory protection can be used. With MP deallocating memory can
219 * cause immediate blocking of access to it, so RemTask() needs to move
220 * the stack to some safe place and make sure that task structure is not
221 * accessed after freeing it.
223 if (GET_THIS_TASK
->tc_State
!= TS_REMOVED
)
224 MUNGE_BLOCK(memoryBlock
, MEMFILL_FREE
, byteSize
);
226 /* Return real start of the block to deallocate */
227 memoryBlock
= header
;
234 * Scan the whole allocations list, optionally removing entries
235 * belonging to a particular pool.
237 void MungWall_Scan(APTR pool
, struct TraceLocation
*loc
, struct ExecBase
*SysBase
)
239 struct IntExecBase
*sysBase
= PrivExecBase(SysBase
);
240 if (sysBase
->IntFlags
& EXECF_MungWall
)
242 struct MungwallHeader
*allocnode
;
243 struct MungwallHeader
*tmp
;
245 DSCAN(bug("[Mungwall] Scan(), caller %s, SysBase 0x%p\n", function
, SysBase
);)
249 ForeachNodeSafe(&sysBase
->AllocMemList
, allocnode
, tmp
)
251 DSCAN(bug("[Mungwall] allocnode 0x%p, next 0x%p, %s(%lu)\n", allocnode
, tmp
, allocnode
->mwh_AllocFunc
, allocnode
->mwh_allocsize
);)
253 if (pool
&& (allocnode
->mwh_pool
== pool
))
256 * If pool address is given, remove entries belonging to it.
257 * It's DeletePool() and they are going to be freed.
258 * Additionally we reset fault state on them. This will cause
259 * one more alert and we can track where the memory was freed.
260 * This will give us a hint on who was owning it.
262 Remove((struct Node
*)allocnode
);
263 allocnode
->mwh_fault
= FALSE
;
266 CheckHeader(allocnode
, 0, loc
, SysBase
);