OpenVPN 3 Core Library
Loading...
Searching...
No Matches
lzoasym_impl.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// This is a special OpenVPN-specific implementation of LZO decompression.
13// It is generally only used when OpenVPN is built without linkage to the
14// actual LZO library, but where we want to maintain compatibility with
15// peers that might send us LZO-compressed packets.
16//
17// It is significantly faster than LZO 2 on ARM because it makes heavy use
18// of branch prediction hints.
19
20#ifndef OPENVPN_COMPRESS_LZOASYM_IMPL_H
21#define OPENVPN_COMPRESS_LZOASYM_IMPL_H
22
23#include <cstdint> // for std::uint32_t, etc.
24#include <cstring> // for memcpy, memmove
25#include <algorithm>
26
27#include <openvpn/common/size.hpp> // for ssize_t
28#include <openvpn/common/likely.hpp> // for likely/unlikely
29
30// Implementation of asymmetrical LZO compression (only uncompress, don't compress)
31
32// Branch prediction hints (these make a difference on ARM)
33#define LZOASYM_LIKELY(x) likely(x)
34#define LZOASYM_UNLIKELY(x) unlikely(x)
35
36// Failure modes
37#define LZOASYM_CHECK_INPUT_OVERFLOW(x) \
38 if (LZOASYM_UNLIKELY(int(input_ptr_end - input_ptr) < int(x))) \
39 { \
40 goto input_overflow; \
41 }
42#define LZOASYM_CHECK_OUTPUT_OVERFLOW(x) \
43 if (LZOASYM_UNLIKELY(int(output_ptr_end - output_ptr) < int(x))) \
44 { \
45 goto output_overflow; \
46 }
47#define LZOASYM_CHECK_MATCH_OVERFLOW(match_ptr) \
48 if (LZOASYM_UNLIKELY(match_ptr < output) \
49 || LZOASYM_UNLIKELY(match_ptr >= output_ptr)) \
50 { \
51 goto match_overflow; \
52 }
53#define LZOASYM_ASSERT(cond) \
54 if (LZOASYM_UNLIKELY(!(cond))) \
55 { \
56 goto assert_fail; \
57 }
58
60// Return status values
61enum
62{
71};
72
73// Internal constants
74enum
75{
78};
79
80// Polymorphic get/set/copy
81
82template <typename T>
83inline T get_mem(const void *p)
84{
85 typedef volatile const T *cptr;
86 return *cptr(p);
87}
88
89// take the number of objects difference between two pointers
90template <typename T>
91inline size_t ptr_diff(const T *a, const T *b)
92{
93 return a - b;
94}
95
96// read uint16_t from memory
97inline size_t get_u16(const unsigned char *p)
98{
99 return p[0] | (p[1] << 8);
100}
101
117inline void incremental_copy(unsigned char *dest, const unsigned char *src, ssize_t len)
118{
119 size_t copylen = dest - src;
120 while (len > 0)
121 {
122 memcpy(dest, src, std::min((size_t)len, (size_t)copylen));
123 dest += copylen;
124 len -= copylen;
125
126 /* we can double copylen every time
127 * we copied the pattern */
128 copylen = copylen * 2;
129 }
130}
131
132
133
134inline int lzo1x_decompress_safe(const unsigned char *input,
135 size_t input_length,
136 unsigned char *output,
137 size_t *output_length)
138{
139 size_t z;
140 const unsigned char *input_ptr;
141 unsigned char *output_ptr;
142 const unsigned char *match_ptr;
143 const unsigned char *const input_ptr_end = input + input_length;
144 unsigned char *const output_ptr_end = output + *output_length;
145
146 *output_length = 0;
147
148 input_ptr = input;
149 output_ptr = output;
150
151 if (LZOASYM_UNLIKELY(input_length > 65536)) // quick fix to prevent 16MB integer overflow vulnerability
152 goto input_too_large;
153
154 if (LZOASYM_LIKELY(*input_ptr <= 17))
155 {
156 while (LZOASYM_LIKELY(input_ptr < input_ptr_end) && LZOASYM_LIKELY(output_ptr <= output_ptr_end))
157 {
158 z = *input_ptr++;
159 if (z < 16) // literal data?
160 {
161 if (LZOASYM_UNLIKELY(z == 0))
162 {
164 while (LZOASYM_UNLIKELY(*input_ptr == 0))
165 {
166 z += 255;
167 input_ptr++;
169 }
170 z += 15 + *input_ptr++;
171 }
172
173 // copy literal data
174 {
175 LZOASYM_ASSERT(z > 0);
176 const size_t len = z + 3;
179 memcpy(output_ptr, input_ptr, len);
180 input_ptr += len;
181 output_ptr += len;
182 }
183
184 initial_literal:
185 z = *input_ptr++;
186 if (LZOASYM_UNLIKELY(z < 16))
187 {
188 match_ptr = output_ptr - (1 + LZOASYM_M2_MAX_OFFSET);
189 match_ptr -= z >> 2;
190 match_ptr -= *input_ptr++ << 2;
191
194 *output_ptr++ = *match_ptr++;
195 *output_ptr++ = *match_ptr++;
196 *output_ptr++ = *match_ptr;
197 goto match_complete;
198 }
199 }
200
201 // found a match (M2, M3, M4, or M1)
202 do
203 {
204 if (LZOASYM_LIKELY(z >= 64)) // LZO "M2" match (most likely)
205 {
206 match_ptr = output_ptr - 1;
207 match_ptr -= (z >> 2) & 7;
208 match_ptr -= *input_ptr++ << 3;
209 z = (z >> 5) - 1;
210 }
211 else if (LZOASYM_LIKELY(z >= 32)) // LZO "M3" match
212 {
213 z &= 31;
214 if (LZOASYM_UNLIKELY(z == 0))
215 {
217 while (LZOASYM_UNLIKELY(*input_ptr == 0))
218 {
219 z += 255;
220 input_ptr++;
222 }
223 z += 31 + *input_ptr++;
224 }
225
226 match_ptr = output_ptr - 1;
227 match_ptr -= get_u16(input_ptr) >> 2;
228 input_ptr += 2;
229 }
230 else if (LZOASYM_LIKELY(z >= 16)) // LZO "M4" match
231 {
232 match_ptr = output_ptr;
233 match_ptr -= (z & 8) << 11;
234 z &= 7;
235 if (LZOASYM_UNLIKELY(z == 0))
236 {
238 while (LZOASYM_UNLIKELY(*input_ptr == 0))
239 {
240 z += 255;
241 input_ptr++;
243 }
244 z += 7 + *input_ptr++;
245 }
246
247 match_ptr -= get_u16(input_ptr) >> 2;
248 input_ptr += 2;
249 if (LZOASYM_UNLIKELY(match_ptr == output_ptr))
250 goto success;
251 match_ptr -= 0x4000;
252 }
253 else // LZO "M1" match (least likely)
254 {
255 match_ptr = output_ptr - 1;
256 match_ptr -= z >> 2;
257 match_ptr -= *input_ptr++ << 2;
258
261 *output_ptr++ = *match_ptr++;
262 *output_ptr++ = *match_ptr;
263 goto match_complete;
264 }
265
266 // copy the match we found above
267 {
269 LZOASYM_ASSERT(z > 0);
271
272 const size_t len = z + 2;
273 incremental_copy(output_ptr, match_ptr, len);
274 output_ptr += len;
275 }
276
277 match_complete:
278 z = input_ptr[-2] & 3;
279 if (LZOASYM_LIKELY(z == 0))
280 break;
281
282 match_continue:
283 // copy literal data
284 LZOASYM_ASSERT(z > 0);
285 LZOASYM_ASSERT(z < 4);
288 *output_ptr++ = *input_ptr++;
289 if (LZOASYM_LIKELY(z > 1))
290 {
291 *output_ptr++ = *input_ptr++;
292 if (z > 2)
293 *output_ptr++ = *input_ptr++;
294 }
295 z = *input_ptr++;
296 } while (LZOASYM_LIKELY(input_ptr < input_ptr_end) && LZOASYM_LIKELY(output_ptr <= output_ptr_end));
297 }
298 }
299 else
300 {
301 // input began with a match or a literal (rare)
302 z = *input_ptr++ - 17;
303 if (z < 4)
304 goto match_continue;
305 LZOASYM_ASSERT(z > 0);
308 do
309 {
310 *output_ptr++ = *input_ptr++;
311 } while (--z > 0);
312 goto initial_literal;
313 }
314
315 *output_length = ptr_diff(output_ptr, output);
317
318success:
319 LZOASYM_ASSERT(z == 1);
320 *output_length = ptr_diff(output_ptr, output);
321 return (input_ptr == input_ptr_end
323 : (input_ptr < input_ptr_end ? LZOASYM_E_INPUT_NOT_CONSUMED : LZOASYM_E_INPUT_OVERFLOW));
324
325input_overflow:
326 *output_length = ptr_diff(output_ptr, output);
328
329output_overflow:
330 *output_length = ptr_diff(output_ptr, output);
332
333match_overflow:
334 *output_length = ptr_diff(output_ptr, output);
336
337assert_fail:
339
340input_too_large:
342}
343} // namespace openvpn::lzo_asym_impl
344
345#undef LZOASYM_CHECK_INPUT_OVERFLOW
346#undef LZOASYM_CHECK_OUTPUT_OVERFLOW
347#undef LZOASYM_CHECK_MATCH_OVERFLOW
348#undef LZOASYM_ASSERT
349#undef LZOASYM_LIKELY
350#undef LZOASYM_UNLIKELY
351
352#endif
#define LZOASYM_CHECK_INPUT_OVERFLOW(x)
#define LZOASYM_CHECK_OUTPUT_OVERFLOW(x)
#define LZOASYM_ASSERT(cond)
#define LZOASYM_UNLIKELY(x)
#define LZOASYM_CHECK_MATCH_OVERFLOW(match_ptr)
#define LZOASYM_LIKELY(x)
T get_mem(const void *p)
void incremental_copy(unsigned char *dest, const unsigned char *src, ssize_t len)
size_t ptr_diff(const T *a, const T *b)
int lzo1x_decompress_safe(const unsigned char *input, size_t input_length, unsigned char *output, size_t *output_length)
size_t get_u16(const unsigned char *p)
static const char * input[]