Runtime hardening causes certain errors to fail earlier or reduces the chance that errors are further exploited. It does not prove that a program is free from out-of-bounds access, use-after-free, or data races. Understanding what each mechanism protects, the toolchain support it requires, and what it does not cover is more useful than collecting a set of "all enabled" flags.
ASLR and PIE: Relocation Allowance Enables Randomization Space
ASLR randomizes parts of the address layout based on kernel policies. For Linux randomize_va_space, 0 means disabling randomization, 1 randomizes mmap, stack, vDSO, etc., and 2 further includes heap-related randomization. Default values and actual ranges depend on kernel configuration and architecture; not all objects are independently randomized with each allocation. Kernel randomize_va_space
PIE allows the main executable image to be positioned by load offset, and when combined with ASLR, its load address varies; non-PIE main images typically use a link-time fixed layout, but their dynamic libraries, stack, etc., can still be randomized. Therefore, "if not PIE, the entire process has no ASLR" is also inaccurate.
Whether the compiler generates PIE by default is determined by the distribution build and toolchain settings. When explicit control is needed, use -fPIE for compilation and -pie for final linking; whether to use -fPIC when building static libraries is another requirement. You cannot simply add a link switch to a single source file while ignoring object compatibility across the entire project.
ASLR limits predictability but does not prevent addresses from leaking through other errors. fork often inherits the existing layout, while a separate exec re-establishes the image; you should not generalize fixed addresses or random ranges from a specific example to all platforms.
RELRO: Complete Necessary Writes First, Then Tighten Permissions
The linker marks areas suitable for conversion to read-only after relocation with PT_GNU_RELRO, and the dynamic loader adjusts permissions after completing the relevant work. -z relro requests this area, and -z now requests immediate binding; full RELRO usually requires both to work together, and standalone NOW does not automatically create all read-only protections.
Lazy binding requires retaining relevant modifiable locations, and the layout affects which GOT areas can be protected. You cannot guarantee that all bytes are read-only in the same way just by seeing .got or .dynamic names; you should combine program headers, dynamic tags, and actual mappings. GNU ld Options
Immediate resolution moves some work to the startup phase, with costs depending on the scale of symbols and dependencies. RELRO protects the corresponding mappings but cannot prevent function pointers in arbitrary heap objects from being incorrectly overwritten, nor does it prevent programs with permissions from actively changing certain memory policies.
How to Correlate File Scope with Runtime Read-Only Areas
GNU_RELRO in readelf -lW gives the virtual address and length from the link perspective; for PIE, first obtain the main image load offset B, then convert to the runtime range. maps displays intervals by page and permissions, so you should not expect the start and end addresses of a single maps line to exactly match the original byte range of GNU_RELRO. You can use the address conversion method in conjunction with ELF File Format.
Observation
Conclusion That Can Be Drawn
Insufficient To Infer
Has GNU_RELRO
The linker described the candidate protection scope
All relevant pages are currently read-only
Has BIND_NOW / NOW
Requests early resolution of applicable dynamic symbols
Any function pointer is protected
Mapping corresponding range is read-only
Cannot be modified with normal write permissions currently
Permissions will never change during the process lifecycle
Another library in the same directory was not checked
Evidence for the main program is still valid
All loaded objects use the same hardening configuration
When checking plugins and dependency libraries, use their respective objects as units. The main program having a certain flag does not automatically recompile other objects into the same form afterwards.
Why Must Relocation Write First, Then Protect?
Observing options and moments together: with only NOW, function addresses can be resolved early, but this does not automatically result in RELRO protection; with only RELRO, call slots that still need delayed modification may remain outside the protection scope. After protection is complete, the normal heap is still writable, indicating that hardening conclusions must point to specific areas.
Preparing the visual
Why Must Relocation Write First, Then Protect?
RELRO describes the scope to be tightened after loading, and NOW determines whether applicable function binding is completed in advance. Normal heap objects do not automatically become read-only due to these flags.
{"id":"relro-write-window","title":"Why Must Relocation Write First, Then Protect?","summary":"RELRO describes the scope to be tightened after loading, and NOW determines whether applicable function binding is completed in advance. Normal heap objects do not automatically become read-only due to these flags.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"relro\" checked><span data-i18n=\"relro\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"now\"><span data-i18n=\"now\"></span></label><div class=\"presets\" role=\"group\" data-i18n-label=\"phase\"><button data-mode=\"loading\" data-i18n=\"loading\"></button><button data-mode=\"running\" data-i18n=\"running\"></button></div><section class=\"panel\"><h3 data-i18n=\"regions\"></h3><div id=\"regions\"></div></section><div class=\"metrics\"><div><span data-i18n=\"bound\"></span><strong id=\"bound\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#regions .tr{grid-template-columns:2fr 1fr}#regions .tr>*{min-height:4em}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let phase='running';function draw(){const r=$('#relro').checked,n=$('#now').checked,protectedNow=r&&phase==='running';pressed(phase);table('regions',[[t('base'),protectedNow?'R':'RW'],[t('slot'),protectedNow&&n?'R':'RW'],[t('heap'),'RW']]);set('bound',t(n?'eager':'lazy'));$('#regions').dataset.protected=protectedNow&&n;}document.querySelectorAll('input').forEach(e=>e.onchange=draw);document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{phase=e.dataset.mode;draw()});draw();","audio":false,"strings":{"base":"Non-Lazy Relocation Area","bound":"Function Address Resolution","eager":"Completed Before Startup","heading":"Why Must Relocation Write First, Then Protect?","heap":"Normal Heap Object","intro":"Choose build flags and observation moments, compare write permissions of two areas.","lazy":"Can Wait Until First Call","loading":"During Relocation","now":"Link -z now","phase":"Observation Moment","regions":"Current Permissions of Representative Areas","relro":"Link -z relro","reset":"Reset","running":"Startup Protection Complete","scope":"Assumes the linker uses common partial/full RELRO layout, and the loader successfully applies protection; two representative areas, not representing all GOT bytes or all dependency libraries.","scopeLabel":"Model Scope and Conventions","slot":"Lazy Call Slot"}}
Stack protector saves a protection value in selected functions and checks it on specified exit paths. When triggered, it usually enters a failure handler and terminates. The protection scope varies depending on -fstack-protector, -fstack-protector-strong, -fstack-protector-all, and target compiler rules; it cannot be described as placing a fixed 8-byte value in every function.
It mainly detects stack corruption passing through relevant layouts but cannot detect all local variable overwrites, heap overflows, or arbitrary data corruption. The initialization of the protection value depends on the runtime library and architecture; you cannot directly attribute the presence of AT_RANDOM to the randomize_va_space switch. GCC Stack Protection Options
NX, W^X, and Stack Conflict Protection
NX/XN and other page execution permission controls determine whether data pages can be fetched and executed. ELF program headers and runtime mappings determine actual permissions; PT_GNU_STACK can express the need for an executable stack; anonymous heaps and stacks are not all directly described by a single ELF PF_X field.
W^X is a policy of not giving execution capability to the same writable area at the same time. JIT and other runtimes may require explicit permission switching or dual-mapping designs; you cannot check the startup mapping once and assume it remains unchanged for the entire lifecycle.
NX does not prevent reusing existing executable code, nor does it prevent pure data errors. stack-clash-protection avoids crossing protection zones through stack growth detection, etc.; it is another type of mechanism and cannot replace stack canaries.
FORTIFY_SOURCE: Using Object Size Checks for Some Library Calls
FORTIFY provides compile-time or runtime checks for some libc interfaces based on object size information available to the compiler. On supported toolchains, level 3 can use dynamic object size information; the effect depends on optimization, call form, and version.
It does not instrument all C pointer reads and writes, and cannot prove that every memory access is legal. Safe calls may be proven at compile time, leaving no _chk symbol; therefore, "not seeing a certain check symbol" does not directly prove that the option is ineffective. glibc Source Fortification
CET: Hardware Support, Product Markers, and Runtime Enablement Should Be Viewed Separately
CET includes shadow stack and IBT. Shadow stack verifies return addresses against protected copies; IBT checks whether specific indirect call/jmp reach marked entry points. IBT does not check normal ret and cannot be described alone as a mechanism that prevents all ROP.
Support for each feature depends on the CPU; you cannot generalize it as "Intel 11th Gen and later." Linux kernel, runtime libraries, loaders, and binaries also need to jointly support and actually enable it.
The Linux kernel documentation checked in this article distinguishes between user-space shadow stack and kernel IBT support. The presence of SHSTK in ELF notes or ENDBR in assembly is insufficient to prove that the corresponding checks are enabled at runtime; user-space shadow stack can be verified by combining the relevant thread feature fields in /proc/PID/status. -fcf-protection=full is not a guarantee that all CET mechanisms are automatically enabled across any Linux environment. Linux CET Documentation
Purpose of CFI and Sanitizers
CFI constrains certain indirect calls and conversions according to type or control flow models, and specific strategies can be finer-grained than just identifying legal entry points. Clang's different CFI checks have different LTO, visibility, and cross-shared-library requirements; they must be configured according to the selected checks; they still only cover the edges and target sets modeled by them. Clang CFI
ASan, TSan, etc., are mainly used to detect execution-time errors and should not be treated as low-cost, compatibility-free default production sandboxes. For fixing and verifying actual memory errors, see Valgrind and Sanitizers. Kernel slab randomization and objects controlled by kptr_restrict, etc., are also different and cannot be treated as direct fixes for ordinary user-space heap UAF or arbitrary information leaks.
A Build and Check Example with Explicit Parameters
The following is for modern Linux GCC/glibc environments supporting the corresponding options. Existing projects should be placed in the correct targets and compilation/linking stages to avoid mutual overwriting of duplicate definitions. Older toolchains need to choose the supported FORTIFY level based on their capabilities.
First, save the following demo.c. It accepts up to 63 bytes and checks the length before copying to a local array; long inputs are rejected before copying. This normal program is used to check build artifacts while retaining clear boundaries, without needing to cause out-of-bounds access to observe hardening options.
#include<stdio.h>#include<string.h>__attribute__((noinline))staticunsignedchecksum(constchar*input,size_tlength){char buffer[64];memcpy(buffer, input, length);
buffer[length]='\0';unsigned value =0;for(size_t i =0; i < length;++i)
value = value *33u+(unsignedchar)buffer[i];return value;}intmain(intargc,char**argv){if(argc !=2)return2;size_t length =strlen(argv[1]);if(length >=64){fputs("input too long\n", stderr);return2;}printf("%u\n",checksum(argv[1], length));return0;}
The unsigned accumulation in checksum allows modulo wraparound; it is a demonstration operation, not a cryptographic digest. This function is an internal helper, and the length being less than 64 is a precondition for the call; if changed to a public interface, the capacity check should be incorporated into the interface itself.
Then run ./demo abc, expecting output 108966; an empty string should output 0, and 64-byte input should return 2 and report "input too long". Use objdump -d --disassemble=checksum demo to check the actual function; optimization may adjust function names or create specialized variants, in which case combine nm and full disassembly for positioning.
To distinguish between source code verification and hardening verification: long inputs being rejected by the application itself is evidence of length checking; it does not trigger canary or FORTIFY. Whether the compiler retained the local array and protection checks should be judged by the instructions and symbol evidence of the corresponding function. noinline does not prohibit all cross-function analysis: the compiler may still derive the length range from the unique call site, inline memcpy without generating __memcpy_chk. If a certain version generates that symbol, it cannot be inferred that all memcpy calls undergo runtime checks.
Check
Evidence Provided
ELF type, interpreter, and tool identification
Whether the main program forms PIE as expected; ET_DYN alone may also be a shared library
GNU_RELRO program header and NOW dynamic tag
RELRO and immediate binding requests
GNU_STACK permissions
Whether the product requests an executable stack
Disassembly, symbols, and compilation records
Protection and check code for specific functions
Runtime maps, thread features
Current mapping permissions and whether applicable hardware mechanisms are actually enabled
Checking build artifacts and runtime confirmation should be performed in conjunction. Hardening options may also affect startup, code size, plugin interfaces, and debugging; verify actual functionality and representative performance before comparing solutions, avoiding references to fixed percentage overheads.
Writing Verifiable Conclusions for Hardening Checks
"Everything enabled" hides the scope of evidence. A more useful record is: which build/link options produced this specific binary, which program headers or dynamic tags support the conclusion, which functions actually have checks, and which runtime states have been observed.
Conclusion Writing
Why It Is More Accurate
Main program has GNU_RELRO and NOW
Clearly indicates product request, not the runtime state of all dependencies
GNU_STACK does not request execution permissions
Specifies that the check object is stack requirements, not generalizing all anonymous mappings
Stack protection check exists in checksum
Specifies the function, avoiding claims that all functions are protected
Input boundary cases pass
Indicates application functionality verification, not equivalent to triggering hardening mechanisms
CET runtime enablement not yet checked
Indicates evidence gap, cannot use ENDBR instruction as a substitute
When encountering compatibility issues, first locate where the failure occurs—compilation, linking, loading, or specific calls—then narrow down to relevant options. The fact that the program runs after canceling all options only indicates that this set of settings affects behavior, and does not point out which mechanism or which source code contract is problematic.