# Chapter 6: The Hook System ## What Are Hooks? Hooks are Apache's primary extension mechanism. They allow modules to register callback functions that are called at specific points during request processing. Think of hooks as **event listeners**. When Apache reaches a certain phase, it "runs" the hook - calling all registered callbacks in order. ```mermaid %%{init: {"flowchart": {"curve": "basis", "nodeSpacing": 30, "rankSpacing": 20}}}%% flowchart TD subgraph A[ap_run_post_read_request] SSL[mod_ssl
callback] --> LOG[mod_log
callback] --> XYZ[mod_xyz
callback] end A --> B subgraph B[ap_run_translate_name] ALIAS[mod_alias] --> PROXY[mod_proxy] end B --> C[...] ``` ## How Hooks Work Every hook in Apache is generated by a pair of macros: one declares the hook's API, and the other implements the dispatch logic. ### The Hook Macros {httpd}`AP_DECLARE_HOOK` (from `include/ap_hooks.h`) declares a hook's registration and run functions: ```c // In a header file (e.g., http_request.h) AP_DECLARE_HOOK(int, translate_name, (request_rec *r)) ``` This expands (via {httpd}`APR_DECLARE_EXTERNAL_HOOK` in `srclib/apr-util/include/apr_hooks.h`) into three things: 1. **{httpd}`ap_hook_translate_name`** - the registration function modules call 2. **{httpd}`ap_run_translate_name`** - the dispatch function the core calls to invoke all registered callbacks 3. **A global {httpd}`apr_array_header_t`** that stores the list of registered callbacks for this hook {httpd}`AP_IMPLEMENT_HOOK_RUN_FIRST` (from `include/ap_hooks.h`) provides the implementation. It wraps the APR-Util macro: ```c // include/ap_hooks.h #define AP_IMPLEMENT_HOOK_RUN_FIRST(ret, name, args_decl, args_use, decline) \ APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP, ret, name, args_decl, \ args_use, decline) ``` ````{dropdown} Full macro expansion for translate_name For `translate_name`, this expands into three generated functions: ```c // The registration function - called by modules in register_hooks() void ap_hook_translate_name(ap_HOOK_translate_name_t *pf, const char *const *aszPre, const char *const *aszSucc, int nOrder) { ap_LINK_translate_name_t *pHook; if (!_hooks.link_translate_name) { _hooks.link_translate_name = apr_array_make( apr_hook_global_pool, 1, sizeof(ap_LINK_translate_name_t)); apr_hook_sort_register("translate_name", &_hooks.link_translate_name); } pHook = apr_array_push(_hooks.link_translate_name); pHook->pFunc = pf; pHook->aszPredecessors = aszPre; pHook->aszSuccessors = aszSucc; pHook->nOrder = nOrder; pHook->szName = apr_hook_debug_current; if (apr_hook_debug_enabled) apr_hook_debug_show("translate_name", aszPre, aszSucc); } // Accessor for the hook's callback array apr_array_header_t *ap_hook_get_translate_name(void) { return _hooks.link_translate_name; } // The dispatch function - called by the core to run all callbacks int ap_run_translate_name(request_rec *r) { ap_LINK_translate_name_t *pHook; int n; int rv = -1; if (_hooks.link_translate_name) { pHook = (ap_LINK_translate_name_t *)_hooks.link_translate_name->elts; for (n = 0; n < _hooks.link_translate_name->nelts; ++n) { rv = pHook[n].pFunc(r); if (rv != -1) // -1 is DECLINED - keep going break; // Any other value stops the chain } } return rv; } ``` A few things to note: - **Lazy initialization**: The hook's callback array is only created when the first module registers for it ({httpd}`apr_array_make` inside {httpd}`ap_hook_translate_name`). - **{httpd}`apr_hook_sort_register`**: Each hook registers itself with the global sort system so {httpd}`apr_hook_sort_all` can find it later. - **The `_hooks` struct**: All hooks for a module share a static struct (`_hooks`) that holds their callback arrays. This is generated by the macro. - **`DECLINED` is -1**: The `RUN_FIRST` dispatch loop checks `rv != -1` (which is `DECLINED`) to decide whether to continue. Any other return value - `OK (0)`, `DONE`, or an `HTTP_*` error code - stops iteration. ```` There are two dispatch variants: - **`RUN_FIRST`**: Calls callbacks until one returns something other than `DECLINED`. Used by most hooks (translate_name, handler, etc.) - **`RUN_ALL`**: Calls every callback and only stops on error. Used by hooks where all modules should participate (log_transaction, etc.) ### Module Loading and Hook Registration When Apache starts, it discovers all compiled-in modules, calls their `register_hooks` functions, and sorts the resulting callbacks. This is driven by {httpd}`ap_setup_prelinked_modules` in `server/config.c`. When you build Apache with `--enable-mods-static=all` (as the fuzzer does), the build system generates a file called `modules.c` that lists every statically linked module in the {httpd}`ap_prelinked_modules` array: ```c // Generated modules.c module *ap_prelinked_modules[] = { &core_module, &so_module, &http_module, &mod_session, &mod_session_cookie, &mod_session_crypto, // ... every statically compiled module NULL // sentinel }; ``` {httpd}`ap_setup_prelinked_modules` walks this array and initializes each module: ```c // server/config.c (simplified) void ap_setup_prelinked_modules(process_rec *process) { // 1. Walk the prelinked module array for (module **m = ap_prelinked_modules; *m != NULL; m++) { // Assign each module a unique index (module_index) // and call its register_hooks function ap_add_module(*m, process->pconf, NULL); } // 2. After ALL modules have registered their hooks, // sort every hook's callback list apr_hook_sort_all(); } ``` {httpd}`ap_add_module` does the critical work for each module: - Assigns a unique `module_index` (used for per-module config vectors) - Calls the module's `register_hooks()` function, which populates the global hook arrays ### Hook Sorting After every module has registered, {httpd}`apr_hook_sort_all` resolves the final ordering. It iterates over every registered hook and performs a two-phase sort: 1. **Numeric sort** (`qsort` by `nOrder`) - groups callbacks by their priority constant 2. **Topological sort** (`tsort()`) - within the same priority level, resolves predecessor/successor constraints into a valid ordering ```mermaid flowchart TD A["ap_setup_prelinked_modules()"] --> B["for each module in
ap_prelinked_modules[]"] B --> C["ap_add_module(module)"] C --> D["Assign module_index"] D --> E["Call module→register_hooks()"] E --> F["Module calls ap_hook_*() to
register callbacks into
global hook arrays"] F --> B B --> G["apr_hook_sort_all()"] G --> H["for each hook"] H --> I["sort_hook()"] I --> J["1. qsort by nOrder"] J --> K["2. tsort() for predecessor/
successor constraints"] K --> H style A fill:#e74c3c,stroke:#c0392b,color:#000 style G fill:#3498db,stroke:#2980b9,color:#000 ``` The final callback order for any hook is determined by: 1. The `APR_HOOK_*` constant (coarse ordering) 2. The predecessor/successor lists (fine-grained ordering within the same level) 3. Registration order (as a tiebreaker when everything else is equal) ```{note} **Security note**: Hook phase and ordering bugs are a real attack surface. The order of `LoadModule` directives in the config affects hook execution order - changing it can introduce silent inconsistencies that yield useful exploit primitives like header manipulation, auth bypass, or unexpected state reaching downstream handlers. ``` ## Using Hooks ### Registering for a Hook Modules register callbacks in their `register_hooks` function: ```c static int my_translate_name(request_rec *r) { if (should_handle(r)) { r->filename = apr_pstrdup(r->pool, "/my/path"); return OK; } return DECLINED; } static void register_hooks(apr_pool_t *p) { ap_hook_translate_name(my_translate_name, NULL, NULL, APR_HOOK_MIDDLE); } ``` The fourth parameter controls callback ordering: ``` APR_HOOK_REALLY_FIRST (-10) │ mod_ssl pre_connection (needs to wrap socket early) ▼ APR_HOOK_FIRST (0) │ Core handlers, security modules ▼ APR_HOOK_MIDDLE (10) │ Most modules register here │ mod_rewrite, mod_alias, etc. ▼ APR_HOOK_LAST (20) │ Fallback handlers │ mod_autoindex, mod_dir ▼ APR_HOOK_REALLY_LAST (30) │ Final cleanup, logging │ mod_log_config ``` For fine-grained control, specify modules that must run before/after: ```c static const char *predecessors[] = { "mod_alias.c", NULL }; static const char *successors[] = { "mod_proxy.c", NULL }; static void register_hooks(apr_pool_t *p) { // Run after mod_alias, before mod_proxy ap_hook_translate_name(my_translate_name, predecessors, // Must run after these successors, // Must run before these APR_HOOK_MIDDLE); } ``` ### Return Values Return values control how hook execution proceeds: ```c OK // Success - continue processing DECLINED // Not handled - let others try DONE // Request complete - skip remaining phases HTTP_* // HTTP error code - abort with error ``` For `RUN_FIRST` hooks (most hooks), the chain works like this: ```mermaid %%{init: {"flowchart": {"curve": "basis", "nodeSpacing": 80, "rankSpacing": 30}}}%% flowchart LR A["Module A"] --> DA{"Result?"} DA e1@-->|DECLINED| B["Module B"] --> DB{"Result?"} DA e2@-->|OK| H["Request handled"] DA e3@--x|HTTP_*| E["Abort with error"] DB e4@-->|DECLINED| C["Module C"] --> DC{"Result?"} DB e5@-->|OK| H DB e6@--x|HTTP_*| E DC e7@-->|OK| H DC e8@-->|DECLINED| N@{ shape: processes, label: "... next module ..." } DC e9@--x|HTTP_*| E e1@{ curve: linear } e2@{ curve: stepBefore } e3@{ curve: stepAfter } e4@{ curve: linear } e5@{ curve: stepBefore } e6@{ curve: stepAfter } e7@{ curve: stepBefore } e8@{ curve: linear } e9@{ curve: stepAfter } style H fill:#2ecc71,stroke:#27ae60,color:#000 style E fill:#e74c3c,stroke:#c0392b,color:#000 style DA fill:#f39c12,stroke:#e67e22,color:#000 style DB fill:#f39c12,stroke:#e67e22,color:#000 style DC fill:#f39c12,stroke:#e67e22,color:#000 ``` ### Creating Custom Hooks Modules can define their own hooks for other modules to use: ```c // In my_module.h - declare the hook AP_DECLARE_HOOK(int, my_custom_hook, (request_rec *r, const char *data)) // In my_module.c - implement hook infrastructure APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, MY_MODULE, int, my_custom_hook, (request_rec *r, const char *data), (r, data), OK, DECLINED) // Call the hook somewhere in your module int rv = ap_run_my_custom_hook(r, "some data"); // Other modules can now hook: ap_hook_my_custom_hook(their_callback, NULL, NULL, APR_HOOK_MIDDLE); ``` ## Hook Reference ### Request Processing Hooks These hooks run in order for each HTTP request. All have the signature `int (*)(request_rec *r)`: ````{dropdown} 1. Post-Read-Request First chance to examine a request after headers are read. ```c ap_hook_post_read_request(my_post_read, NULL, NULL, APR_HOOK_MIDDLE); static int my_post_read(request_rec *r) { // Log initial request info // Set up per-request state return DECLINED; // Let others run too } ``` ```` ````{dropdown} 2. Translate Name Map URI to filename or handler. ```c ap_hook_translate_name(my_translate, NULL, NULL, APR_HOOK_MIDDLE); static int my_translate(request_rec *r) { if (strncmp(r->uri, "/special/", 9) == 0) { r->filename = apr_pstrcat(r->pool, "/var/special", r->uri + 8, NULL); return OK; // We handled it } return DECLINED; } ``` ```` ````{dropdown} 3. Map to Storage Called after translate_name, before access checking. ```c ap_hook_map_to_storage(my_map, NULL, NULL, APR_HOOK_MIDDLE); ``` ```` ````{dropdown} 4. Header Parser Parse request headers. ```c ap_hook_header_parser(my_header_parser, NULL, NULL, APR_HOOK_MIDDLE); ``` ```` ````{dropdown} 5. Access Checker IP/host-based access control (before authentication). ```c ap_hook_access_checker(my_access_checker, NULL, NULL, APR_HOOK_MIDDLE); static int my_access_checker(request_rec *r) { if (is_banned_ip(r->useragent_ip)) { return HTTP_FORBIDDEN; } return DECLINED; } ``` ```` ````{dropdown} 6. Check User ID (Authentication) Authenticate the user. ```c ap_hook_check_user_id(my_authn, NULL, NULL, APR_HOOK_MIDDLE); static int my_authn(request_rec *r) { const char *auth_header = apr_table_get(r->headers_in, "Authorization"); if (!auth_header) { return DECLINED; } if (validate_auth(auth_header)) { r->user = apr_pstrdup(r->pool, username); return OK; } return HTTP_UNAUTHORIZED; } ``` ```` ````{dropdown} 7. Auth Checker (Authorization) Check if authenticated user is authorized. ```c ap_hook_auth_checker(my_authz, NULL, NULL, APR_HOOK_MIDDLE); static int my_authz(request_rec *r) { if (!r->user) { return DECLINED; } if (user_has_access(r->user, r->uri)) { return OK; } return HTTP_FORBIDDEN; } ``` ```` ````{dropdown} 8. Type Checker Determine content type and handler. ```c ap_hook_type_checker(my_type_checker, NULL, NULL, APR_HOOK_MIDDLE); static int my_type_checker(request_rec *r) { if (r->filename && ends_with(r->filename, ".xyz")) { ap_set_content_type(r, "application/x-xyz"); r->handler = "xyz-handler"; return OK; } return DECLINED; } ``` ```` ````{dropdown} 9. Fixups Last chance to modify request before handler. ```c ap_hook_fixups(my_fixup, NULL, NULL, APR_HOOK_MIDDLE); static int my_fixup(request_rec *r) { apr_table_set(r->headers_out, "X-Processed-By", "MyModule"); return DECLINED; } ``` ```` ````{dropdown} 10. Handler Generate the response content. ```c ap_hook_handler(my_handler, NULL, NULL, APR_HOOK_MIDDLE); static int my_handler(request_rec *r) { if (!r->handler || strcmp(r->handler, "my-handler") != 0) { return DECLINED; } ap_set_content_type(r, "text/plain"); ap_rputs("Hello from my handler!\n", r); return OK; } ``` ```` ````{dropdown} 11. Log Transaction Log the completed request. ```c ap_hook_log_transaction(my_logger, NULL, NULL, APR_HOOK_MIDDLE); static int my_logger(request_rec *r) { log_request(r->uri, r->status, r->bytes_sent); return OK; } ``` ```` ### Connection Hooks These hooks operate at the connection level, before HTTP parsing: ````{dropdown} Pre-Connection Set up connection state, filters, etc. ```c // Signature: int (*)(conn_rec *c, void *csd) ap_hook_pre_connection(my_pre_conn, NULL, NULL, APR_HOOK_MIDDLE); static int my_pre_conn(conn_rec *c, void *csd) { // csd is the socket descriptor ap_add_input_filter("MY_INPUT", NULL, NULL, c); return OK; } ``` ```` ````{dropdown} Process Connection Handle the entire connection (used by protocol modules). ```c // Signature: int (*)(conn_rec *c) ap_hook_process_connection(my_process_conn, NULL, NULL, APR_HOOK_MIDDLE); static int my_process_conn(conn_rec *c) { // Custom protocol handler // Return OK to claim the connection return DECLINED; // Let HTTP handle it } ``` ```` ````{dropdown} Create Connection Create the {httpd}`conn_rec` structure. ```c // Signature: conn_rec* (*)(apr_pool_t *p, server_rec *s, ...) ap_hook_create_connection(my_create_conn, NULL, NULL, APR_HOOK_MIDDLE); ``` ```` ### Server Lifecycle Hooks These hooks run during server startup and shutdown: ````{dropdown} Pre Config Called before configuration is loaded. ```c // Signature: int (*)(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) ap_hook_pre_config(my_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ``` ```` ````{dropdown} Post Config Called after configuration is loaded. ```c // Signature: int (*)(apr_pool_t *pconf, apr_pool_t *plog, // apr_pool_t *ptemp, server_rec *s) ap_hook_post_config(my_post_config, NULL, NULL, APR_HOOK_MIDDLE); static int my_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { // Validate configuration // Allocate shared resources return OK; } ``` ```` ````{dropdown} Open Logs Called when log files should be opened. ```c // Signature: int (*)(apr_pool_t *pconf, apr_pool_t *plog, // apr_pool_t *ptemp, server_rec *s) ap_hook_open_logs(my_open_logs, NULL, NULL, APR_HOOK_MIDDLE); ``` ```` ````{dropdown} Child Init Called when a child process starts. ```c // Signature: void (*)(apr_pool_t *p, server_rec *s) ap_hook_child_init(my_child_init, NULL, NULL, APR_HOOK_MIDDLE); static void my_child_init(apr_pool_t *p, server_rec *s) { // Initialize per-child resources // Open database connections, etc. } ``` ```` ## Complete Example Here's a module using multiple hooks to track request timing: ```c #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_request.h" #include "ap_config.h" module AP_MODULE_DECLARE_DATA example_module; typedef struct { apr_time_t start_time; } example_request_state; // Post-read: record start time static int example_post_read(request_rec *r) { example_request_state *state = apr_pcalloc(r->pool, sizeof(*state)); state->start_time = apr_time_now(); ap_set_module_config(r->request_config, &example_module, state); return DECLINED; } // Fixup: add custom header static int example_fixup(request_rec *r) { apr_table_set(r->headers_out, "X-Example-Module", "active"); return DECLINED; } // Handler: respond to /example static int example_handler(request_rec *r) { if (!r->handler || strcmp(r->handler, "example-handler") != 0) { return DECLINED; } example_request_state *state = ap_get_module_config( r->request_config, &example_module); ap_set_content_type(r, "text/plain"); ap_rputs("Hello from Example Module!\n", r); if (state) { apr_time_t elapsed = apr_time_now() - state->start_time; ap_rprintf(r, "Processing time: %" APR_TIME_T_FMT " microseconds\n", elapsed); } return OK; } // Log: record timing static int example_log(request_rec *r) { example_request_state *state = ap_get_module_config( r->request_config, &example_module); if (state) { apr_time_t elapsed = apr_time_now() - state->start_time; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Request to %s took %" APR_TIME_T_FMT " us", r->uri, elapsed); } return OK; } // Register hooks static void register_hooks(apr_pool_t *p) { ap_hook_post_read_request(example_post_read, NULL, NULL, APR_HOOK_FIRST); ap_hook_fixups(example_fixup, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_log_transaction(example_log, NULL, NULL, APR_HOOK_LAST); } AP_DECLARE_MODULE(example) = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, // No directives register_hooks }; ``` **See also:** [mod_example_hooks.c](https://github.com/omnigroup/Apache/blob/master/httpd/modules/examples/mod_example_hooks.c) (Apache's own hook tracing module) and the official [module development guide](https://httpd.apache.org/docs/2.4/developer/modguide.html). ## Fuzzing Implications ```{important} Understanding hook infrastructure matters for the fuzzing harness: - **Static linking** means {httpd}`ap_prelinked_modules` contains every module we want to fuzz. The harness calls {httpd}`ap_setup_prelinked_modules` during initialization, which registers all hooks exactly as a real Apache would. - **Hook ordering is deterministic** for a given set of compiled modules. This means fuzzing results are reproducible - the same input always hits the same callback chain in the same order. - **The harness can selectively disable modules** by manipulating the module list before {httpd}`ap_setup_prelinked_modules` runs, which is useful for isolating specific code paths during targeted fuzzing. ``` ## Summary Hooks are Apache's plugin system: - {httpd}`AP_DECLARE_HOOK` generates `ap_hook_*` (register) and `ap_run_*` (dispatch) functions from macros - Modules register callbacks in `register_hooks()` with an ordering constant ({httpd}`APR_HOOK_FIRST` / `MIDDLE` / `LAST`) or predecessor/successor lists - `RUN_FIRST` hooks stop on the first non-`DECLINED` return; `RUN_ALL` hooks call every callback - {httpd}`ap_setup_prelinked_modules` loads all modules, calls their `register_hooks`, and {httpd}`apr_hook_sort_all` resolves the final ordering - Return `DECLINED` to pass, `OK` when handled, `HTTP_*` to abort