Skip to content

Commit f704623

Browse files
Aaron DrewAaron Drew
authored andcommitted
Address various unchecked NULLs, deprecated interfaces, etc.
Tried building on MacOS for the first time so I've also made that work.
1 parent 2963960 commit f704623

File tree

8 files changed

+162
-41
lines changed

8 files changed

+162
-41
lines changed

CMakeLists.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.7)
1+
cmake_minimum_required(VERSION 3.10)
22
project(HttpsDnsProxy C)
33

44
# FUNCTIONS
@@ -65,8 +65,21 @@ endif()
6565

6666
# LIBRARY DEPENDENCIES
6767

68+
# Add Homebrew paths for macOS
69+
if(APPLE)
70+
if(EXISTS "/opt/homebrew")
71+
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
72+
link_directories("/opt/homebrew/lib")
73+
elseif(EXISTS "/usr/local")
74+
list(APPEND CMAKE_PREFIX_PATH "/usr/local")
75+
link_directories("/usr/local/lib")
76+
endif()
77+
endif()
78+
6879
find_path(LIBCARES_INCLUDE_DIR ares.h)
6980
find_path(LIBEV_INCLUDE_DIR ev.h)
81+
find_library(LIBCARES_LIBRARY NAMES cares)
82+
find_library(LIBEV_LIBRARY NAMES ev)
7083

7184
if(CUSTOM_LIBCURL_INSTALL_PATH)
7285
message(STATUS "Using custom libcurl from: ${CUSTOM_LIBCURL_INSTALL_PATH}")
@@ -108,7 +121,7 @@ set(TARGET_NAME "https_dns_proxy")
108121
aux_source_directory(src SRC_LIST)
109122
set(SRC_LIST ${SRC_LIST})
110123
add_executable(${TARGET_NAME} ${SRC_LIST})
111-
set(LIBS ${LIBS} cares curl ev resolv)
124+
set(LIBS ${LIBS} ${LIBCARES_LIBRARY} curl ${LIBEV_LIBRARY} resolv)
112125
target_link_libraries(${TARGET_NAME} ${LIBS})
113126
set_property(TARGET ${TARGET_NAME} PROPERTY C_STANDARD 11)
114127

src/dns_poller.c

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,40 @@ static void sock_state_cb(void *data, int fd, int read, int write) {
4444
ev_io_start(d->loop, io_event_ptr);
4545
}
4646

47-
static char *get_addr_listing(char** addr_list, const int af) {
47+
48+
static char *get_addrinfo_listing(struct ares_addrinfo *ai) {
4849
char *list = (char *)calloc(1, POLLER_ADDR_LIST_SIZE);
4950
char *pos = list;
5051
if (list == NULL) {
5152
FLOG("Out of mem");
5253
}
53-
for (int i = 0; addr_list[i]; i++) {
54-
const char *res = ares_inet_ntop(af, addr_list[i], pos,
55-
list + POLLER_ADDR_LIST_SIZE - 1 - pos);
54+
55+
for (struct ares_addrinfo_node *node = ai->nodes; node; node = node->ai_next) {
56+
size_t remaining = POLLER_ADDR_LIST_SIZE - (pos - list) - 1;
57+
if (remaining < INET6_ADDRSTRLEN + 1) { // +1 for comma
58+
WLOG("Address list buffer too small, truncating");
59+
break;
60+
}
61+
62+
const char *res = NULL;
63+
if (node->ai_family == AF_INET) {
64+
struct sockaddr_in *sin = (struct sockaddr_in *)node->ai_addr;
65+
res = ares_inet_ntop(AF_INET, &sin->sin_addr, pos, remaining);
66+
} else if (node->ai_family == AF_INET6) {
67+
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)node->ai_addr;
68+
res = ares_inet_ntop(AF_INET6, &sin6->sin6_addr, pos, remaining);
69+
}
70+
5671
if (res != NULL) {
5772
pos += strlen(pos);
73+
if (pos - list >= POLLER_ADDR_LIST_SIZE - 1) {
74+
break; // prevent overflow
75+
}
5876
*pos = ',';
5977
pos++;
6078
}
6179
}
80+
6281
if (pos == list) {
6382
free((void*)list);
6483
list = NULL;
@@ -68,19 +87,23 @@ static char *get_addr_listing(char** addr_list, const int af) {
6887
return list;
6988
}
7089

71-
static void ares_cb(void *arg, int status, int __attribute__((unused)) timeouts,
72-
struct hostent *h) {
90+
static void ares_addrinfo_cb(void *arg, int status, int __attribute__((unused)) timeouts,
91+
struct ares_addrinfo *ai) {
7392
dns_poller_t *d = (dns_poller_t *)arg;
7493
d->request_ongoing = 0;
7594
ev_tstamp interval = 5; // retry by default after some time
7695

7796
if (status != ARES_SUCCESS) {
7897
WLOG("DNS lookup of '%s' failed: %s", d->hostname, ares_strerror(status));
79-
} else if (!h || h->h_length < 1) {
98+
} else if (!ai || !ai->nodes) {
8099
WLOG("No hosts found for '%s'", d->hostname);
81100
} else {
82101
interval = d->polling_interval;
83-
d->cb(d->hostname, d->cb_data, get_addr_listing(h->h_addr_list, h->h_addrtype));
102+
d->cb(d->hostname, d->cb_data, get_addrinfo_listing(ai));
103+
}
104+
105+
if (ai) {
106+
ares_freeaddrinfo(ai);
84107
}
85108

86109
if (status != ARES_EDESTRUCTION) {
@@ -91,14 +114,15 @@ static void ares_cb(void *arg, int status, int __attribute__((unused)) timeouts,
91114
}
92115
}
93116

117+
94118
static ev_tstamp get_timeout(dns_poller_t *d)
95119
{
96120
static struct timeval max_tv = {.tv_sec = 5, .tv_usec = 0};
97121
struct timeval tv;
98122
struct timeval *tvp = ares_timeout(d->ares, &max_tv, &tv);
99123
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
100124
ev_tstamp after = tvp->tv_sec + tvp->tv_usec * 1e-6;
101-
return after ? after : 0.1;
125+
return after > 0 ? after : 0.1;
102126
}
103127

104128
static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
@@ -108,7 +132,7 @@ static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
108132
if (d->request_ongoing) {
109133
// process query timeouts
110134
DLOG("Processing DNS queries");
111-
ares_process(d->ares, NULL, NULL);
135+
ares_process_fds(d->ares, NULL, 0, 0);
112136
} else {
113137
DLOG("Starting DNS query");
114138
// Cancel any pending queries before making new ones. c-ares can't be depended on to
@@ -117,7 +141,9 @@ static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
117141
// free memory tied up by any "zombie" queries.
118142
ares_cancel(d->ares);
119143
d->request_ongoing = 1;
120-
ares_gethostbyname(d->ares, d->hostname, d->family, ares_cb, d);
144+
struct ares_addrinfo_hints hints = {0};
145+
hints.ai_family = d->family;
146+
ares_getaddrinfo(d->ares, d->hostname, NULL, &hints, ares_addrinfo_cb, d);
121147
}
122148

123149
if (d->request_ongoing) { // need to re-check, it might change!
@@ -134,6 +160,10 @@ void dns_poller_init(dns_poller_t *d, struct ev_loop *loop,
134160
int bootstrap_dns_polling_interval,
135161
const char *hostname,
136162
int family, dns_poller_cb cb, void *cb_data) {
163+
if (!d || !loop || !bootstrap_dns || !hostname || !cb) {
164+
FLOG("Invalid parameters to dns_poller_init");
165+
}
166+
137167
int r = ares_library_init(ARES_LIB_INIT_ALL);
138168
if (r != ARES_SUCCESS) {
139169
FLOG("ares_library_init error: %s", ares_strerror(r));

src/dns_server.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,13 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop,
7373
&tmp_addrlen);
7474
if (len < 0) {
7575
ELOG("recvfrom failed: %s", strerror(errno));
76+
free(buf);
7677
return;
7778
}
7879

7980
if (len < (int)sizeof(uint16_t)) {
8081
WLOG("Malformed request received (too short).");
82+
free(buf);
8183
return;
8284
}
8385

src/https_client.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
#define DOH_CONTENT_TYPE "application/dns-message"
1818
enum {
19-
DOH_MAX_RESPONSE_SIZE = 65535
19+
DOH_MAX_RESPONSE_SIZE = 65535,
20+
HTTPS_TIMEOUT_WITH_CONNECTIONS = 5,
21+
HTTPS_TIMEOUT_WITHOUT_CONNECTIONS = 10
2022
};
2123

2224
// the following macros require to have ctx pointer to https_fetch_ctx structure
@@ -310,10 +312,10 @@ static void https_fetch_ctx_init(https_client_t *client,
310312
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_WRITEDATA, ctx);
311313
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_MAXAGE_CONN, client->opt->max_idle_time);
312314
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_PIPEWAIT, client->opt->use_http_version > 1);
313-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_USERAGENT, "https_dns_proxy/0.3");
315+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_USERAGENT, "https_dns_proxy");
314316
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_FOLLOWLOCATION, 0);
315317
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_NOSIGNAL, 0);
316-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TIMEOUT, client->connections > 0 ? 5 : 10 /* seconds */);
318+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TIMEOUT, client->connections > 0 ? HTTPS_TIMEOUT_WITH_CONNECTIONS : HTTPS_TIMEOUT_WITHOUT_CONNECTIONS);
317319
// We know Google supports this, so force it.
318320
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
319321
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_ERRORBUFFER, ctx->curl_errbuf); // zeroed by calloc

src/main.c

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ typedef struct {
3636
struct sockaddr_storage raddr;
3737
} request_t;
3838

39-
static int is_ipv4_address(char *str) {
39+
static int is_ipv4_address(const char *str) {
4040
struct in6_addr addr;
4141
return inet_pton(AF_INET, str, &addr) == 1;
4242
}
4343

4444
static int hostname_from_url(const char* url_in,
4545
char* hostname, const size_t hostname_len) {
46+
if (!url_in || !hostname || hostname_len == 0) {
47+
return 0;
48+
}
4649
int res = 0;
4750
CURLU *url = curl_url();
4851
if (url != NULL) {
@@ -54,8 +57,9 @@ static int hostname_from_url(const char* url_in,
5457
if (rc == CURLUE_OK && host_len < hostname_len &&
5558
host[0] != '[' && host[host_len-1] != ']' && // skip IPv6 address
5659
!is_ipv4_address(host)) {
57-
strncpy(hostname, host, hostname_len-1); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
58-
hostname[hostname_len-1] = '\0';
60+
size_t copy_len = host_len < hostname_len - 1 ? host_len : hostname_len - 1;
61+
memcpy(hostname, host, copy_len);
62+
hostname[copy_len] = '\0';
5963
res = 1; // success
6064
}
6165
curl_free(host);
@@ -80,10 +84,11 @@ static void sigpipe_cb(struct ev_loop __attribute__((__unused__)) *loop,
8084

8185
static void https_resp_cb(void *data, char *buf, size_t buflen) {
8286
request_t *req = (request_t *)data;
83-
DLOG("Received response for id: %hX, len: %zu", req->tx_id, buflen);
8487
if (req == NULL) {
85-
FLOG("%04hX: data NULL", req->tx_id);
88+
FLOG("data NULL in https_resp_cb");
89+
return;
8690
}
91+
DLOG("Received response for id: %hX, len: %zu", req->tx_id, buflen);
8792
free((void*)req->dns_req);
8893
if (buf != NULL) { // May be NULL for timeout, DNS failure, or something similar.
8994
if (buflen < (int)sizeof(uint16_t)) {
@@ -107,6 +112,11 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) {
107112
static void dns_server_cb(dns_server_t *dns_server, void *data,
108113
struct sockaddr* addr, uint16_t tx_id,
109114
char *dns_req, size_t dns_req_len) {
115+
if (!dns_server || !data || !addr || !dns_req) {
116+
ELOG("NULL pointer in dns_server_cb");
117+
if (dns_req) free(dns_req);
118+
return;
119+
}
110120
app_state_t *app = (app_state_t *)data;
111121

112122
DLOG("Received request for id: %hX, len: %d", tx_id, dns_req_len);
@@ -139,34 +149,63 @@ static void dns_server_cb(dns_server_t *dns_server, void *data,
139149
}
140150

141151
static int addr_list_reduced(const char* full_list, const char* list) {
152+
if (!full_list || !list) {
153+
return 1;
154+
}
155+
142156
const char *pos = list;
143-
const char *end = list + strlen(list);
157+
const size_t list_len = strlen(list);
158+
const char *end = list + list_len;
159+
144160
while (pos < end) {
145-
char current[50];
161+
char current[INET6_ADDRSTRLEN];
146162
const char *comma = strchr(pos, ',');
147-
size_t ip_len = comma ? comma - pos : end - pos;
148-
strncpy(current, pos, ip_len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
163+
size_t ip_len = comma ? (size_t)(comma - pos) : (size_t)(end - pos);
164+
165+
if (ip_len == 0 || ip_len >= sizeof(current)) {
166+
DLOG("Invalid IP address length: %zu", ip_len);
167+
return 1;
168+
}
169+
170+
memcpy(current, pos, ip_len);
149171
current[ip_len] = '\0';
150172

151-
const char *match_begin = strstr(full_list, current);
152-
if (!match_begin ||
153-
!(match_begin == full_list || *(match_begin - 1) == ',') ||
154-
!(*(match_begin + ip_len) == ',' || *(match_begin + ip_len) == '\0')) {
173+
// More efficient search: check if the current IP exists as a complete token
174+
const char *search_pos = full_list;
175+
int found = 0;
176+
177+
while ((search_pos = strstr(search_pos, current)) != NULL) {
178+
// Check if it's a complete token (preceded and followed by delimiter or boundary)
179+
int is_start = (search_pos == full_list || *(search_pos - 1) == ',');
180+
int is_end = (*(search_pos + ip_len) == ',' || *(search_pos + ip_len) == '\0');
181+
182+
if (is_start && is_end) {
183+
found = 1;
184+
break;
185+
}
186+
search_pos += ip_len;
187+
}
188+
189+
if (!found) {
155190
DLOG("IP address missing: %s", current);
156191
return 1;
157192
}
158193

159-
pos += ip_len + 1;
194+
pos = comma ? comma + 1 : end;
160195
}
161196
return 0;
162197
}
163198

164199
static void dns_poll_cb(const char* hostname, void *data,
165200
const char* addr_list) {
201+
if (!hostname || !data || !addr_list) {
202+
ELOG("NULL pointer in dns_poll_cb");
203+
return;
204+
}
166205
app_state_t *app = (app_state_t *)data;
167206
char buf[255 + (sizeof(":443:") - 1) + POLLER_ADDR_LIST_SIZE];
168207
memset(buf, 0, sizeof(buf)); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
169-
if (strlen(hostname) > 254) { FLOG("Hostname too long."); }
208+
if (strlen(hostname) > 253) { FLOG("Hostname too long."); }
170209
int ip_start = snprintf(buf, sizeof(buf) - 1, "%s:443:", hostname); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
171210
(void)snprintf(buf + ip_start, sizeof(buf) - 1 - ip_start, "%s", addr_list); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
172211
if (app->resolv && app->resolv->data) {
@@ -205,7 +244,7 @@ static int proxy_supports_name_resolution(const char *proxy)
205244
return 0;
206245
}
207246

208-
static const char * sw_version(void) {
247+
static const char *sw_version(void) {
209248
#ifdef SW_VERSION
210249
return SW_VERSION;
211250
#else
@@ -315,10 +354,27 @@ int main(int argc, char *argv[]) {
315354
}
316355

317356
if (opt.daemonize) {
318-
// daemon() is non-standard. If needed, see OpenSSH openbsd-compat/daemon.c
319-
if (daemon(0, 0) == -1) {
320-
FLOG("daemon failed: %s", strerror(errno));
357+
// Use fork() and setsid() instead of deprecated daemon()
358+
pid_t pid = fork();
359+
if (pid < 0) {
360+
FLOG("fork failed: %s", strerror(errno));
361+
} else if (pid > 0) {
362+
exit(0); // parent exits
363+
}
364+
365+
if (setsid() < 0) {
366+
FLOG("setsid failed: %s", strerror(errno));
321367
}
368+
369+
// Change working directory to root
370+
if (chdir("/") < 0) {
371+
FLOG("chdir failed: %s", strerror(errno));
372+
}
373+
374+
// Close standard file descriptors
375+
close(STDIN_FILENO);
376+
close(STDOUT_FILENO);
377+
close(STDERR_FILENO);
322378
}
323379

324380
ev_signal sigpipe;

src/options.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,24 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char *
123123

124124
if (opt->user) {
125125
struct passwd *p = getpwnam(opt->user);
126-
if (!p || !p->pw_uid) {
127-
printf("Username (%s) invalid.\n", opt->user);
126+
if (!p) {
127+
printf("Username (%s) not found.\n", opt->user);
128+
return OPR_OPTION_ERROR;
129+
}
130+
if (p->pw_uid == 0) {
131+
printf("Username (%s) is root (uid 0), which is not allowed.\n", opt->user);
128132
return OPR_OPTION_ERROR;
129133
}
130134
opt->uid = p->pw_uid;
131135
}
132136
if (opt->group) {
133137
struct group *g = getgrnam(opt->group);
134-
if (!g || !g->gr_gid) {
135-
printf("Group (%s) invalid.\n", opt->group);
138+
if (!g) {
139+
printf("Group (%s) not found.\n", opt->group);
140+
return OPR_OPTION_ERROR;
141+
}
142+
if (g->gr_gid == 0) {
143+
printf("Group (%s) is root (gid 0), which is not allowed.\n", opt->group);
136144
return OPR_OPTION_ERROR;
137145
}
138146
opt->gid = g->gr_gid;

0 commit comments

Comments
 (0)