Coverage Report

Created: 2026-06-16 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tor/src/lib/process/env.c
Line
Count
Source
1
/* Copyright (c) 2003-2004, Roger Dingledine
2
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3
 * Copyright (c) 2007-2021, The Tor Project, Inc. */
4
/* See LICENSE for licensing information */
5
6
/**
7
 * \file env.c
8
 * \brief Inspect and manipulate the environment variables.
9
 **/
10
11
#include "orconfig.h"
12
#include "lib/process/env.h"
13
14
#include "lib/malloc/malloc.h"
15
#include "lib/ctime/di_ops.h"
16
#include "lib/container/smartlist.h"
17
#include "lib/log/util_bug.h"
18
#include "lib/log/log.h"
19
20
#ifdef HAVE_UNISTD_H
21
#include <unistd.h>
22
#endif
23
#include <stdlib.h>
24
#include <string.h>
25
#ifdef HAVE_CRT_EXTERNS_H
26
/* For _NSGetEnviron on macOS */
27
#include <crt_externs.h>
28
#endif
29
30
#ifndef HAVE__NSGETENVIRON
31
#ifndef HAVE_EXTERN_ENVIRON_DECLARED
32
/* Some platforms declare environ under some circumstances, others don't. */
33
#ifndef RUNNING_DOXYGEN
34
extern char **environ;
35
#endif
36
#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */
37
#endif /* !defined(HAVE__NSGETENVIRON) */
38
39
/** Return the current environment. This is a portable replacement for
40
 * 'environ'. */
41
char **
42
get_environment(void)
43
0
{
44
#ifdef HAVE__NSGETENVIRON
45
  /* This is for compatibility between OSX versions.  Otherwise (for example)
46
   * when we do a mostly-static build on OSX 10.7, the resulting binary won't
47
   * work on OSX 10.6. */
48
  return *_NSGetEnviron();
49
#else /* !defined(HAVE__NSGETENVIRON) */
50
0
  return environ;
51
0
#endif /* defined(HAVE__NSGETENVIRON) */
52
0
}
53
54
/** Helper: return the number of characters in <b>s</b> preceding the first
55
 * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return
56
 * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */
57
static inline size_t
58
str_num_before(const char *s, char ch)
59
0
{
60
0
  const char *cp = strchr(s, ch);
61
0
  if (cp)
62
0
    return cp - s;
63
0
  else
64
0
    return strlen(s);
65
0
}
66
67
/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b>
68
 * to have the same name as strings in a process's environment. */
69
int
70
environment_variable_names_equal(const char *s1, const char *s2)
71
0
{
72
0
  size_t s1_name_len = str_num_before(s1, '=');
73
0
  size_t s2_name_len = str_num_before(s2, '=');
74
75
0
  return (s1_name_len == s2_name_len &&
76
0
          tor_memeq(s1, s2, s1_name_len));
77
0
}
78
79
/** Free <b>env</b> (assuming it was produced by
80
 * process_environment_make). */
81
void
82
process_environment_free_(process_environment_t *env)
83
0
{
84
0
  if (env == NULL) return;
85
86
  /* As both an optimization hack to reduce consing on Unixoid systems
87
   * and a nice way to ensure that some otherwise-Windows-specific
88
   * code will always get tested before changes to it get merged, the
89
   * strings which env->unixoid_environment_block points to are packed
90
   * into env->windows_environment_block. */
91
0
  tor_free(env->unixoid_environment_block);
92
0
  tor_free(env->windows_environment_block);
93
94
0
  tor_free(env);
95
0
}
96
97
/** Make a process_environment_t containing the environment variables
98
 * specified in <b>env_vars</b> (as C strings of the form
99
 * "NAME=VALUE"). */
100
process_environment_t *
101
process_environment_make(struct smartlist_t *env_vars)
102
0
{
103
0
  process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
104
0
  int n_env_vars = smartlist_len(env_vars);
105
0
  int i;
106
0
  size_t total_env_length;
107
0
  smartlist_t *env_vars_sorted;
108
109
0
  tor_assert(n_env_vars + 1 != 0);
110
0
  env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *));
111
  /* env->unixoid_environment_block is already NULL-terminated,
112
   * because we assume that NULL == 0 (and check that during compilation). */
113
114
0
  total_env_length = 1; /* terminating NUL of terminating empty string */
115
0
  for (i = 0; i < n_env_vars; ++i) {
116
0
    const char *s = smartlist_get(env_vars, (int)i);
117
0
    size_t slen = strlen(s);
118
119
0
    tor_assert(slen + 1 != 0);
120
0
    tor_assert(slen + 1 < SIZE_MAX - total_env_length);
121
0
    total_env_length += slen + 1;
122
0
  }
123
124
0
  env->windows_environment_block = tor_malloc_zero(total_env_length);
125
  /* env->windows_environment_block is already
126
   * (NUL-terminated-empty-string)-terminated. */
127
128
  /* Some versions of Windows supposedly require that environment
129
   * blocks be sorted.  Or maybe some Windows programs (or their
130
   * runtime libraries) fail to look up strings in non-sorted
131
   * environment blocks.
132
   *
133
   * Also, sorting strings makes it easy to find duplicate environment
134
   * variables and environment-variable strings without an '=' on all
135
   * OSes, and they can cause badness.  Let's complain about those. */
136
0
  env_vars_sorted = smartlist_new();
137
0
  smartlist_add_all(env_vars_sorted, env_vars);
138
0
  smartlist_sort_strings(env_vars_sorted);
139
140
  /* Now copy the strings into the environment blocks. */
141
0
  {
142
0
    char *cp = env->windows_environment_block;
143
0
    const char *prev_env_var = NULL;
144
145
0
    for (i = 0; i < n_env_vars; ++i) {
146
0
      const char *s = smartlist_get(env_vars_sorted, (int)i);
147
0
      size_t slen = strlen(s);
148
0
      size_t s_name_len = str_num_before(s, '=');
149
150
0
      if (s_name_len == slen) {
151
0
        log_warn(LD_GENERAL,
152
0
                 "Preparing an environment containing a variable "
153
0
                 "without a value: %s",
154
0
                 s);
155
0
      }
156
0
      if (prev_env_var != NULL &&
157
0
          environment_variable_names_equal(s, prev_env_var)) {
158
0
        log_warn(LD_GENERAL,
159
0
                 "Preparing an environment containing two variables "
160
0
                 "with the same name: %s and %s",
161
0
                 prev_env_var, s);
162
0
      }
163
164
0
      prev_env_var = s;
165
166
      /* Actually copy the string into the environment. */
167
0
      memcpy(cp, s, slen+1);
168
0
      env->unixoid_environment_block[i] = cp;
169
0
      cp += slen+1;
170
0
    }
171
172
0
    tor_assert(cp == env->windows_environment_block + total_env_length - 1);
173
0
  }
174
175
0
  smartlist_free(env_vars_sorted);
176
177
0
  return env;
178
0
}
179
180
/** Return a newly allocated smartlist containing every variable in
181
 * this process's environment, as a NUL-terminated string of the form
182
 * "NAME=VALUE".  Note that on some/many/most/all OSes, the parent
183
 * process can put strings not of that form in our environment;
184
 * callers should try to not get crashed by that.
185
 *
186
 * The returned strings are heap-allocated, and must be freed by the
187
 * caller. */
188
struct smartlist_t *
189
get_current_process_environment_variables(void)
190
0
{
191
0
  smartlist_t *sl = smartlist_new();
192
193
0
  char **environ_tmp; /* Not const char ** ? Really? */
194
0
  for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) {
195
0
    smartlist_add_strdup(sl, *environ_tmp);
196
0
  }
197
198
0
  return sl;
199
0
}
200
201
/** For each string s in <b>env_vars</b> such that
202
 * environment_variable_names_equal(s, <b>new_var</b>), remove it; if
203
 * <b>free_p</b> is non-zero, call <b>free_old</b>(s).  If
204
 * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */
205
void
206
set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
207
                                      const char *new_var,
208
                                      void (*free_old)(void*),
209
                                      int free_p)
210
0
{
211
0
  SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
212
0
    if (environment_variable_names_equal(s, new_var)) {
213
0
      SMARTLIST_DEL_CURRENT(env_vars, s);
214
0
      if (free_p) {
215
0
        free_old((void *)s);
216
0
      }
217
0
    }
218
0
  } SMARTLIST_FOREACH_END(s);
219
220
0
  if (strchr(new_var, '=') != NULL) {
221
0
    smartlist_add(env_vars, (void *)new_var);
222
0
  }
223
0
}