|
22 | 22 | #include "httpserver/iovec_entry.hpp" |
23 | 23 |
|
24 | 24 | #include <cstddef> |
| 25 | +#include <limits> |
25 | 26 | #include <microhttpd.h> |
26 | 27 | #include <sys/uio.h> |
| 28 | +#include <type_traits> |
27 | 29 | #include <vector> |
28 | 30 |
|
29 | 31 | struct MHD_Response; |
@@ -66,25 +68,64 @@ static_assert(offsetof(::httpserver::iovec_entry, len) == |
66 | 68 | offsetof(MHD_IoVec, iov_len), |
67 | 69 | "iovec_entry::len offset must match MHD_IoVec::iov_len"); |
68 | 70 |
|
| 71 | +// Alignment pinning: ensures the reinterpret_cast array stride is safe on |
| 72 | +// architectures that trap on misaligned loads (SPARC, some ARM configs). |
| 73 | +// CWE-704: without alignof equality the cast is UB even when size/offset match. |
| 74 | +static_assert(alignof(::httpserver::iovec_entry) == alignof(struct iovec), |
| 75 | + "iovec_entry alignment must match POSIX struct iovec — divergent platform; " |
| 76 | + "implement memcpy fallback (see TASK-004)"); |
| 77 | +static_assert(alignof(::httpserver::iovec_entry) == alignof(MHD_IoVec), |
| 78 | + "iovec_entry alignment must match MHD_IoVec — MHD layout drift"); |
| 79 | + |
| 80 | +// Standard-layout guarantee: required so that reinterpret_cast between |
| 81 | +// pointer-interconvertible types is well-defined under -fstrict-aliasing. |
| 82 | +static_assert(std::is_standard_layout_v<::httpserver::iovec_entry>, |
| 83 | + "iovec_entry must be standard layout for reinterpret_cast to MHD_IoVec"); |
| 84 | + |
| 85 | +iovec_response::iovec_response( |
| 86 | + std::vector<std::string> owned_buffers, |
| 87 | + int response_code, |
| 88 | + const std::string& content_type) |
| 89 | + : http_response(response_code, content_type), |
| 90 | + owned_buffers_(std::move(owned_buffers)) { |
| 91 | + // Build the iovec_entry array eagerly so get_raw_response() is |
| 92 | + // allocation-free on the hot dispatch path. |
| 93 | + entries_.reserve(owned_buffers_.size()); |
| 94 | + for (const auto& b : owned_buffers_) { |
| 95 | + entries_.push_back({b.data(), b.size()}); |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +iovec_response::iovec_response( |
| 100 | + std::vector<iovec_entry> caller_entries, |
| 101 | + int response_code, |
| 102 | + const std::string& content_type) |
| 103 | + : http_response(response_code, content_type), |
| 104 | + entries_(std::move(caller_entries)) { |
| 105 | + // owned_buffers_ is empty — buffer ownership stays with the caller. |
| 106 | +} |
| 107 | + |
69 | 108 | MHD_Response* iovec_response::get_raw_response() { |
70 | | - // MHD_create_response_from_iovec makes an internal copy of the iov array, |
71 | | - // so the local vector is safe. The buffer data pointed to by iov_base must |
72 | | - // remain valid until the response is destroyed — this is guaranteed because |
73 | | - // the buffers are owned by this iovec_response object. |
74 | | - // |
75 | | - // The dispatch path builds a contiguous std::vector<iovec_entry> from the |
76 | | - // owned std::strings, then reinterpret_casts it to const MHD_IoVec* when |
77 | | - // calling MHD. The cast is well-defined because the layout-pinning |
78 | | - // static_asserts above guarantee identical size and field offsets. This |
79 | | - // same cast bridge will move into details/body.hpp when TASK-009 lands. |
80 | | - std::vector<iovec_entry> entries(buffers.size()); |
81 | | - for (size_t i = 0; i < buffers.size(); ++i) { |
82 | | - entries[i].base = buffers[i].data(); |
83 | | - entries[i].len = buffers[i].size(); |
| 109 | + // Guard against integer narrowing: MHD_create_response_from_iovec takes |
| 110 | + // an unsigned int count. A vector with more than UINT_MAX entries would |
| 111 | + // silently truncate, causing MHD to read only part of the array while the |
| 112 | + // reported body length diverges from the actual allocation (CWE-190, |
| 113 | + // CWE-125). Return nullptr (the documented MHD "error" sentinel) instead. |
| 114 | + if (entries_.size() > |
| 115 | + static_cast<std::size_t>( |
| 116 | + std::numeric_limits<unsigned int>::max())) { |
| 117 | + return nullptr; |
84 | 118 | } |
| 119 | + |
| 120 | + // The reinterpret_cast is well-defined because the layout-pinning |
| 121 | + // static_asserts above guarantee identical size, field offsets, and |
| 122 | + // alignment between iovec_entry and MHD_IoVec (C++ [basic.align], |
| 123 | + // CWE-704). entries_ was populated at construction time: no heap |
| 124 | + // allocation occurs on this path. The cast bridge will move into |
| 125 | + // details/body.hpp when TASK-009 lands. |
85 | 126 | return MHD_create_response_from_iovec( |
86 | | - reinterpret_cast<const MHD_IoVec*>(entries.data()), |
87 | | - static_cast<unsigned int>(entries.size()), |
| 127 | + reinterpret_cast<const MHD_IoVec*>(entries_.data()), |
| 128 | + static_cast<unsigned int>(entries_.size()), |
88 | 129 | nullptr, |
89 | 130 | nullptr); |
90 | 131 | } |
|
0 commit comments