Botan 3.13.0
Crypto and TLS for C&
compress_utils.cpp
Go to the documentation of this file.
1/*
2* Compression Utils
3* (C) 2014,2016 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/internal/compress_utils.h>
9
10#include <botan/exceptn.h>
11#include <botan/mem_ops.h>
12#include <botan/internal/fmt.h>
13#include <botan/internal/int_utils.h>
14#include <cstdlib>
15
16namespace Botan {
17
18Compression_Error::Compression_Error(const char* func_name, ErrorType type, int rc) :
19 Exception(fmt("Compression API {} failed with return code {}", func_name, rc)), m_type(type), m_rc(rc) {}
20
21void* Compression_Alloc_Info::do_malloc(size_t n, size_t size) noexcept {
22 // Precheck for integer overflow in the multiplication
23 // before passing to calloc, which may or may not check.
24 if(!checked_mul(n, size)) {
25 return nullptr;
26 }
27
28 void* ptr = std::calloc(n, size); // NOLINT(*-no-malloc,*-owning-memory,*-const-correctness)
29
30 if(ptr == nullptr) {
31 return nullptr;
32 }
33
34 /*
35 * Return null rather than throwing here as we are being called by a
36 * C library and it may not be possible for an exception to unwind
37 * the call stack from here. The compression library is expecting a
38 * function written in C and a null return on error, which it will
39 * send upwards to the compression wrappers. So if recording the
40 * allocation throws (eg bad_alloc growing the map), free the block and
41 * report failure the same way.
42 */
43 try {
44 m_current_allocs[ptr] = n * size;
45 } catch(...) {
46 std::free(ptr); // NOLINT(*-no-malloc,*-owning-memory)
47 return nullptr;
48 }
49
50 return ptr;
51}
52
53void Compression_Alloc_Info::do_free(void* ptr) noexcept {
54 if(ptr != nullptr) {
55 auto i = m_current_allocs.find(ptr);
56
57 if(i == m_current_allocs.end()) {
58 /*
59 * The compression library tried to free a pointer that was not allocated by
60 * a call to the allocation function. We do not have any particularly good
61 * options here.
62 *
63 * - Throwing is not possible since it would unwind through a C ABI library,
64 * which results in undefined behavior.
65 *
66 * - We could panic (fprintf to stderr and abort) but causing a unilateral
67 * and unrecoverable process crash isn't necessarily the right choice either.
68 *
69 * - We could skip the scrub step and pass the pointer along directly to
70 * std::free, but that risks corrupting the system heap.
71 *
72 * So instead we just ignore the request entirely
73 */
74 return;
75 }
76
77 secure_scrub_memory(ptr, i->second);
78 std::free(ptr); // NOLINT(*-no-malloc,*-owning-memory)
79 m_current_allocs.erase(i);
80 }
81}
82
84 m_stream.reset();
85}
86
87void Stream_Compression::start(size_t level) {
88 m_stream = make_stream(level);
89}
90
91void Stream_Compression::process(secure_vector<uint8_t>& buf, size_t offset, uint32_t flags) {
92 BOTAN_ASSERT(m_stream, "Initialized");
93 BOTAN_ASSERT(buf.size() >= offset, "Offset is sane");
94
95 // bzip doesn't like being called with no input and BZ_RUN
96 if(buf.size() == offset && flags == m_stream->run_flag()) {
97 return;
98 }
99
100 const size_t buffer_needed = add_or_throw(buf.size(), offset, "Compression input too large");
101 if(m_buffer.size() < buffer_needed) {
102 m_buffer.resize(buffer_needed);
103 }
104
105 // If the output buffer has zero length, .data() might return nullptr. This would
106 // make some compression algorithms (notably those provided by zlib) fail.
107 // Any small positive value works fine, but we choose 32 as it is the smallest power
108 // of two that is large enough to hold all the headers and trailers of the common
109 // formats, preventing further resizings to make room for output data.
110 if(m_buffer.empty()) {
111 m_buffer.resize(32);
112 }
113
114 m_stream->next_in(buf.data() + offset, buf.size() - offset);
115 m_stream->next_out(m_buffer.data() + offset, m_buffer.size() - offset);
116
117 while(true) {
118 const bool stream_end = m_stream->run(flags);
119
120 if(stream_end) {
121 BOTAN_ASSERT(m_stream->avail_in() == 0, "After stream is done, no input remains to be processed");
122 m_buffer.resize(m_buffer.size() - m_stream->avail_out());
123 break;
124 } else if(m_stream->avail_out() == 0) {
125 const size_t added = add_or_throw<size_t>(m_buffer.size(), 8, "Compression output too large");
126 const size_t new_size = add_or_throw(m_buffer.size(), added, "Compression output too large");
127 m_buffer.resize(new_size);
128 m_stream->next_out(m_buffer.data() + m_buffer.size() - added, added);
129 } else if(m_stream->avail_in() == 0) {
130 m_buffer.resize(m_buffer.size() - m_stream->avail_out());
131 break;
132 }
133 }
134
135 copy_mem(m_buffer.data(), buf.data(), offset);
136 buf.swap(m_buffer);
137}
138
139void Stream_Compression::update(secure_vector<uint8_t>& buf, size_t offset, bool flush) {
140 BOTAN_ASSERT(m_stream, "Initialized");
141 process(buf, offset, flush ? m_stream->flush_flag() : m_stream->run_flag());
142}
143
145 BOTAN_ASSERT(m_stream, "Initialized");
146 process(buf, offset, m_stream->finish_flag());
147 clear();
148}
149
151 m_stream.reset();
152}
153
155 m_stream = make_stream();
156}
157
158void Stream_Decompression::process(secure_vector<uint8_t>& buf, size_t offset, uint32_t flags) {
159 BOTAN_ASSERT(m_stream, "Initialized");
160 BOTAN_ASSERT(buf.size() >= offset, "Offset is sane");
161
162 const size_t buffer_needed = add_or_throw(buf.size(), offset, "Compression input too large");
163 if(m_buffer.size() < buffer_needed) {
164 m_buffer.resize(buffer_needed);
165 }
166
167 m_stream->next_in(buf.data() + offset, buf.size() - offset);
168 m_stream->next_out(m_buffer.data() + offset, m_buffer.size() - offset);
169
170 while(true) {
171 const bool stream_end = m_stream->run(flags);
172
173 if(stream_end) {
174 if(m_stream->avail_in() == 0) {
175 // all data consumed
176 m_buffer.resize(m_buffer.size() - m_stream->avail_out());
177 clear();
178 break;
179 }
180
181 // More data follows: try to process as a following stream
182 // Remove stream1's unused output space so stream2's output
183 // is placed immediately after stream1's data with no gap.
184 m_buffer.resize(m_buffer.size() - m_stream->avail_out());
185 const size_t read = (buf.size() - offset) - m_stream->avail_in();
186 start();
187 m_stream->next_in(buf.data() + offset + read, buf.size() - offset - read);
188 }
189
190 if(m_stream->avail_out() == 0) {
191 const size_t added = add_or_throw<size_t>(m_buffer.size(), 8, "Compression output too large");
192 const size_t new_size = add_or_throw(m_buffer.size(), added, "Compression output too large");
193 m_buffer.resize(new_size);
194 m_stream->next_out(m_buffer.data() + m_buffer.size() - added, added);
195 } else if(m_stream->avail_in() == 0) {
196 m_buffer.resize(m_buffer.size() - m_stream->avail_out());
197 break;
198 }
199 }
200
201 copy_mem(m_buffer.data(), buf.data(), offset);
202 buf.swap(m_buffer);
203}
204
206 if(!m_stream) {
207 if(buf.size() == offset) {
208 return;
209 }
210 // Previous stream ended cleanly; re-initialize for a concatenated stream
211 start();
212 }
213 process(buf, offset, m_stream->run_flag());
214}
215
217 if(!m_stream) {
218 if(buf.size() == offset) {
219 return;
220 }
221 // Previous stream ended cleanly; re-initialize for a concatenated stream
222 start();
223 }
224
225 process(buf, offset, m_stream->finish_flag());
226
227 if(m_stream) {
228 throw Invalid_State(fmt("{} finished but not at stream end", name()));
229 }
230}
231
232} // namespace Botan
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:62
Compression_Error(const char *func_name, ErrorType type, int rc)
virtual std::string name() const =0
Exception(std::string_view msg)
Definition exceptn.cpp:71
void update(secure_vector< uint8_t > &buf, size_t offset, bool flush) final
void finish(secure_vector< uint8_t > &buf, size_t offset) final
void update(secure_vector< uint8_t > &buf, size_t offset) final
void finish(secure_vector< uint8_t > &buf, size_t offset) final
constexpr T add_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:66
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
void secure_scrub_memory(void *ptr, size_t n)
Definition mem_utils.cpp:25
constexpr std::optional< T > checked_mul(T a, T b)
Definition int_utils.h:46
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
ErrorType
Definition exceptn.h:21