Skip to content

Commit f4bb08a

Browse files
Add: New data stream validation utilities (#936)
* Add: New data stream validation utilities New utility functions have been added to allow validating the size and checksum / hash of contents of a data stream like a file being read. * Unit test the new stream validator * Adjust formatting of stream validator code * Use SHA256 for validator tests, fix typo. * Amend stream validator function documentation
1 parent 67e48c9 commit f4bb08a

5 files changed

Lines changed: 501 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ if(BUILD_TESTS AND NOT SKIP_SRC)
248248
nvti-test
249249
osp-test
250250
passwordbasedauthentication-test
251+
streamvalidator-test
251252
test-hosts
252253
util-test
253254
version-test

util/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ set(
135135
radiusutils.c
136136
serverutils.c
137137
sshutils.c
138+
streamvalidator.c
138139
uuidutils.c
139140
versionutils.c
140141
vtparser.c
@@ -158,6 +159,7 @@ set(
158159
radiusutils.h
159160
serverutils.h
160161
sshutils.h
162+
streamvalidator.h
161163
uuidutils.h
162164
versionutils.h
163165
vtparser.h
@@ -293,6 +295,15 @@ if(BUILD_TESTS)
293295
${GNUTLS_LDFLAGS}
294296
${LINKER_HARDENING_FLAGS}
295297
)
298+
add_unit_test(
299+
streamvalidator-test
300+
streamvalidator_tests.c
301+
gvm_util_shared
302+
gvm_base_shared
303+
${GLIB_LDFLAGS}
304+
${GCRYPT_LDFLAGS}
305+
${LINKER_HARDENING_FLAGS}
306+
)
296307
add_unit_test(
297308
versionutils-test
298309
versionutils_tests.c

util/streamvalidator.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/* SPDX-FileCopyrightText: 2025 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: GPL-2.0-or-later
4+
*/
5+
6+
#include "streamvalidator.h"
7+
8+
#include "authutils.h"
9+
10+
#include <assert.h>
11+
#include <gcrypt.h>
12+
#include <glib.h>
13+
14+
/**
15+
* @file
16+
* @brief Data stream validation.
17+
*/
18+
19+
/**
20+
* @brief Data stream validator structure.
21+
*/
22+
struct gvm_stream_validator
23+
{
24+
gchar *expected_hash_str; ///< Expected hash algorithm and hex string.
25+
gchar *expected_hash_hex; ///< Expected hash value as hexadecimal string.
26+
int algorithm; ///< The hash algorithm used.
27+
size_t expected_size; ///< Expected amount of data to validate.
28+
size_t current_size; ///< Current total amount of data received.
29+
gcry_md_hd_t gcrypt_md_hd; ///< gcrypt message digest handle.
30+
};
31+
32+
/**
33+
* @brief Gets a string representation of a gvm_stream_validator_return_t
34+
*
35+
* @param[in] value The value to get a string representation of.
36+
*
37+
* @return Static string describing the return value
38+
* or NULL if value is GVM_STREAM_VALIDATOR_OK.
39+
*/
40+
const char *
41+
gvm_stream_validator_return_str (gvm_stream_validator_return_t value)
42+
{
43+
switch (value)
44+
{
45+
case GVM_STREAM_VALIDATOR_INTERNAL_ERROR:
46+
return "internal error";
47+
case GVM_STREAM_VALIDATOR_OK:
48+
return NULL;
49+
case GVM_STREAM_VALIDATOR_DATA_TOO_SHORT:
50+
return "too short";
51+
case GVM_STREAM_VALIDATOR_DATA_TOO_LONG:
52+
return "too long";
53+
case GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX:
54+
return "invalid hash syntax";
55+
case GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM:
56+
return "invalid or unsupported hash algorithm";
57+
case GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE:
58+
return "invalid hash value";
59+
case GVM_STREAM_VALIDATOR_HASH_MISMATCH:
60+
return "hash does not match";
61+
default:
62+
return "unknown error";
63+
}
64+
}
65+
66+
/**
67+
* @brief Allocate and initialize a new data stream validator.
68+
*
69+
* @param[in] expected_hash_str Expected hash / checksum string consisting of
70+
* an algorithm name or OID as recognized by
71+
* gcrypt, followed by a colon and the
72+
* hex-encoded hash,
73+
* e.g. "md5:70165459812a0d38851a4a4c3e4124c9".
74+
* @param[in] expected_size The number of bytes expected to be sent.
75+
* @param[out] validator_out Pointer to output location of the newly allocated
76+
* validator.
77+
*
78+
* @return A validator return code, returning a failure if the expeced hash
79+
* string is invalid or uses an unsupported algorithm.
80+
*/
81+
gvm_stream_validator_return_t
82+
gvm_stream_validator_new (const char *expected_hash_str, size_t expected_size,
83+
gvm_stream_validator_t *validator_out)
84+
{
85+
assert (validator_out);
86+
87+
static GRegex *hex_regex = NULL;
88+
gchar **split_hash_str = g_strsplit (expected_hash_str, ":", 2);
89+
const char *algo_str, *hex_str;
90+
int algo;
91+
unsigned int expected_hex_len;
92+
gcry_md_hd_t gcrypt_md_hd;
93+
94+
if (hex_regex == NULL)
95+
hex_regex = g_regex_new ("^(?:[0-9A-Fa-f][0-9A-Fa-f])+$", G_REGEX_DEFAULT,
96+
G_REGEX_MATCH_DEFAULT, NULL);
97+
98+
*validator_out = NULL;
99+
if (g_strv_length (split_hash_str) != 2)
100+
{
101+
g_strfreev (split_hash_str);
102+
return GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX;
103+
}
104+
algo_str = split_hash_str[0];
105+
hex_str = split_hash_str[1];
106+
107+
algo = gcry_md_map_name (algo_str);
108+
if (algo == GCRY_MD_NONE || gcry_md_test_algo (algo))
109+
{
110+
g_strfreev (split_hash_str);
111+
return GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM;
112+
}
113+
114+
expected_hex_len = gcry_md_get_algo_dlen (algo) * 2;
115+
if (strlen (hex_str) != expected_hex_len
116+
|| g_regex_match (hex_regex, hex_str, 0, NULL) == FALSE)
117+
{
118+
g_strfreev (split_hash_str);
119+
return GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE;
120+
}
121+
122+
gcrypt_md_hd = NULL;
123+
if (gcry_md_open (&gcrypt_md_hd, algo, 0))
124+
{
125+
g_strfreev (split_hash_str);
126+
return GVM_STREAM_VALIDATOR_INTERNAL_ERROR;
127+
}
128+
129+
*validator_out = g_malloc0 (sizeof (struct gvm_stream_validator));
130+
(*validator_out)->algorithm = algo;
131+
(*validator_out)->expected_size = expected_size;
132+
(*validator_out)->expected_hash_str = g_strdup (expected_hash_str);
133+
(*validator_out)->expected_hash_hex = g_strdup (hex_str);
134+
(*validator_out)->gcrypt_md_hd = gcrypt_md_hd;
135+
136+
g_strfreev (split_hash_str);
137+
138+
return GVM_STREAM_VALIDATOR_OK;
139+
}
140+
141+
/**
142+
* @brief Rewind the validation state of a stream validator while keeping the
143+
* expected hash and data size.
144+
*
145+
* @param[in] validator The validator to rewind.
146+
*/
147+
void
148+
gvm_stream_validator_rewind (gvm_stream_validator_t validator)
149+
{
150+
gcry_md_reset (validator->gcrypt_md_hd);
151+
validator->current_size = 0;
152+
}
153+
154+
/**
155+
* @brief Free a stream validator and all of its fields.
156+
*
157+
* @param[in] validator The validator to free.
158+
*/
159+
void
160+
gvm_stream_validator_free (gvm_stream_validator_t validator)
161+
{
162+
gcry_md_close (validator->gcrypt_md_hd);
163+
g_free (validator->expected_hash_str);
164+
g_free (validator->expected_hash_hex);
165+
g_free (validator);
166+
}
167+
168+
/**
169+
* @brief Write data to a validator, updating the hash state and current size.
170+
*
171+
* Will fail if the total data size exceeds the expected size.
172+
*
173+
* @param[in] validator The validator to handle the data
174+
* @param[in] data The data to write.
175+
* @param[in] length Length of the data.
176+
*
177+
* @return Validator return code, either a "success" or "too long".
178+
*/
179+
gvm_stream_validator_return_t
180+
gvm_stream_validator_write (gvm_stream_validator_t validator, const char *data,
181+
size_t length)
182+
{
183+
if (length > validator->expected_size - validator->current_size)
184+
return GVM_STREAM_VALIDATOR_DATA_TOO_LONG;
185+
186+
gcry_md_write (validator->gcrypt_md_hd, data, length);
187+
validator->current_size += length;
188+
189+
return GVM_STREAM_VALIDATOR_OK;
190+
}
191+
192+
/**
193+
* @brief Signal the end of data input into a validator and produce the result
194+
* of the validation.
195+
*
196+
* @param[in] validator The validator to signal the end of data input of.
197+
*
198+
* @return The validation result.
199+
*/
200+
gvm_stream_validator_return_t
201+
gvm_stream_validator_end (gvm_stream_validator_t validator)
202+
{
203+
unsigned char *actual_hash_bin;
204+
gchar *actual_hash_hex;
205+
206+
if (validator->current_size < validator->expected_size)
207+
return GVM_STREAM_VALIDATOR_DATA_TOO_SHORT;
208+
209+
if (validator->current_size > validator->expected_size)
210+
return GVM_STREAM_VALIDATOR_DATA_TOO_LONG;
211+
212+
actual_hash_bin =
213+
gcry_md_read (validator->gcrypt_md_hd, validator->algorithm);
214+
actual_hash_hex = digest_hex (validator->algorithm, actual_hash_bin);
215+
if (strcasecmp (validator->expected_hash_hex, actual_hash_hex))
216+
{
217+
g_free (actual_hash_hex);
218+
return GVM_STREAM_VALIDATOR_HASH_MISMATCH;
219+
}
220+
g_free (actual_hash_hex);
221+
222+
return GVM_STREAM_VALIDATOR_OK;
223+
}

util/streamvalidator.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* SPDX-FileCopyrightText: 2025 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: GPL-2.0-or-later
4+
*/
5+
6+
#ifndef _GVM_STREAMVALIDATOR_H
7+
#define _GVM_STREAMVALIDATOR_H
8+
9+
#include <stdio.h>
10+
11+
/**
12+
* @file
13+
* @brief Data stream validation headers.
14+
*/
15+
16+
typedef enum
17+
{
18+
/** An internal error ocurred. */
19+
GVM_STREAM_VALIDATOR_INTERNAL_ERROR = -1,
20+
/** Action successful / data is valid. */
21+
GVM_STREAM_VALIDATOR_OK = 0,
22+
/** Not enough data received. */
23+
GVM_STREAM_VALIDATOR_DATA_TOO_SHORT = 1,
24+
/** Too much data received. */
25+
GVM_STREAM_VALIDATOR_DATA_TOO_LONG,
26+
/** Syntax error in hash string (not using "algo:hex" format). */
27+
GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX,
28+
/** Invalid or unsupported hash algorithm. */
29+
GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM,
30+
/** Hash value is not valid.
31+
* (e.g. not hexadecimal or length does not match algorithm) */
32+
GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE,
33+
/** Hash of received data does not match the expected hash */
34+
GVM_STREAM_VALIDATOR_HASH_MISMATCH
35+
} gvm_stream_validator_return_t;
36+
37+
/**
38+
* @brief Pointer to an opaque stream validator data structure.
39+
*/
40+
typedef struct gvm_stream_validator *gvm_stream_validator_t;
41+
42+
const char *gvm_stream_validator_return_str (gvm_stream_validator_return_t);
43+
44+
gvm_stream_validator_return_t
45+
gvm_stream_validator_new (const char *, size_t, gvm_stream_validator_t *);
46+
47+
void gvm_stream_validator_rewind (gvm_stream_validator_t);
48+
49+
void gvm_stream_validator_free (gvm_stream_validator_t);
50+
51+
gvm_stream_validator_return_t
52+
gvm_stream_validator_write (gvm_stream_validator_t, const char *, size_t);
53+
54+
gvm_stream_validator_return_t gvm_stream_validator_end (gvm_stream_validator_t);
55+
56+
#endif /* not _GVM_STREAMVALIDATOR_H */

0 commit comments

Comments
 (0)