Add helper function to compile a regex that supports (?i).

This commit is contained in:
Todd C. Miller
2022-02-11 12:01:31 -07:00
parent 86d2173937
commit 7c17f84a35
23 changed files with 231 additions and 140 deletions

View File

@@ -245,6 +245,7 @@ lib/util/progname.c
lib/util/pw_dup.c
lib/util/pwrite.c
lib/util/rcstr.c
lib/util/regex.c
lib/util/reallocarray.c
lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1
lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2

View File

@@ -16,7 +16,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.TH "SUDO_LOGSRVD.CONF" "@mansectform@" "February 10, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.TH "SUDO_LOGSRVD.CONF" "@mansectform@" "February 11, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -641,6 +641,9 @@ One or more POSIX extended regular expressions used to
match password prompts in the terminal output when
\fIlog_passwords\fR
is disabled.
As an extension, if the regular expression begins with
\(lq(?i)\(rq,
it will be matched in a case-insensitive manner.
Multiple
\fIpassprompt_regex\fR
settings may be specified.

View File

@@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd February 10, 2022
.Dd February 11, 2022
.Dt SUDO_LOGSRVD.CONF @mansectform@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -571,6 +571,9 @@ One or more POSIX extended regular expressions used to
match password prompts in the terminal output when
.Em log_passwords
is disabled.
As an extension, if the regular expression begins with
.Dq (?i) ,
it will be matched in a case-insensitive manner.
Multiple
.Em passprompt_regex
settings may be specified.

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
.TH "SUDOERS" "@mansectform@" "February 10, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.TH "SUDOERS" "@mansectform@" "February 11, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -5424,6 +5424,9 @@ This setting is only supported by version 1.9.0 or higher.
passprompt_regex
A list of POSIX extended regular expressions used to
match password prompts in the terminal output.
As an extension, if the regular expression begins with
\(lq(?i)\(rq,
it will be matched in a case-insensitive manner.
This option is only used when
\fIlog_passwords\fR
has been disabled.

View File

@@ -24,7 +24,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
.Dd February 10, 2022
.Dd February 11, 2022
.Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -5062,6 +5062,9 @@ This setting is only supported by version 1.9.0 or higher.
.It passprompt_regex
A list of POSIX extended regular expressions used to
match password prompts in the terminal output.
As an extension, if the regular expression begins with
.Dq (?i) ,
it will be matched in a case-insensitive manner.
This option is only used when
.Em log_passwords
has been disabled.

View File

@@ -256,6 +256,10 @@ sudo_dso_public char *sudo_rcstr_alloc(size_t len);
sudo_dso_public char *sudo_rcstr_addref(const char *s);
sudo_dso_public void sudo_rcstr_delref(const char *s);
/* regex.c */
sudo_dso_public bool sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr);
#define sudo_regex_compile(_a, _b, _c) sudo_regex_compile_v1((_a), (_b), (_c))
/* roundup.c */
sudo_dso_public unsigned int sudo_pow2_roundup_v1(unsigned int len);
#define sudo_pow2_roundup(_a) sudo_pow2_roundup_v1((_a))

View File

@@ -41,6 +41,7 @@
#include "sudo_gettext.h"
#include "sudo_iolog.h"
#include "sudo_queue.h"
#include "sudo_util.h"
struct pwfilt_regex {
TAILQ_ENTRY(pwfilt_regex) entries;
@@ -110,51 +111,6 @@ iolog_pwfilt_free(void *vhandle)
debug_return;
}
/*
* Like strdup but collapses repeated '?', '*' and '+' ops in a regex.
* Glibc regcomp() has a bug where it uses excessive memory for repeated
* '+' ops. Collapse them to avoid running the fuzzer out of memory.
*/
static char *
dup_pattern(const char *src)
{
char *dst, *ret;
char ch, prev = '\0';
size_t len;
debug_decl(dup_pattern, SUDO_DEBUG_UTIL);
len = strlen(src);
ret = malloc(len + 1);
if (ret == NULL)
debug_return_ptr(NULL);
dst = ret;
while ((ch = *src++) != '\0') {
switch (ch) {
case '\\':
if (*src != '\0') {
*dst++ = '\\';
*dst++ = *src++;
prev = '\0';
continue;
}
break;
case '?':
case '*':
case '+':
if (ch == prev) {
continue;
}
break;
}
*dst++ = ch;
prev = ch;
}
*dst = '\0';
debug_return_ptr(ret);
}
/*
* Add a pattern to the password filter list.
*/
@@ -163,22 +119,19 @@ iolog_pwfilt_add(void *vhandle, const char *pattern)
{
struct pwfilt_handle *handle = vhandle;
struct pwfilt_regex *filt;
char errbuf[1024];
int errcode;
const char *errstr;
debug_decl(iolog_pwfilt_add, SUDO_DEBUG_UTIL);
filt = malloc(sizeof(*filt));
if (filt == NULL)
goto oom;
filt->pattern = dup_pattern(pattern);
filt->pattern = strdup(pattern);
if (filt->pattern == NULL)
goto oom;
errcode = regcomp(&filt->regex, filt->pattern, REG_EXTENDED|REG_NOSUB);
if (errcode != 0) {
regerror(errcode, &filt->regex, errbuf, sizeof(errbuf));
if (!sudo_regex_compile(&filt->regex, filt->pattern, &errstr)) {
sudo_warnx(U_("invalid regular expression \"%s\": %s"),
pattern, errbuf);
pattern, U_(errstr));
goto bad;
}

View File

@@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2011-2021 Todd C. Miller <Todd.Miller@sudo.ws>
# Copyright (c) 2011-2022 Todd C. Miller <Todd.Miller@sudo.ws>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -140,9 +140,9 @@ SHELL = @SHELL@
LTOBJS = basename.lo @DIGEST@ event.lo fatal.lo key_val.lo gethostname.lo \
gettime.lo getgrouplist.lo gidlist.lo json.lo lbuf.lo locking.lo \
logfac.lo logpri.lo mkdir_parents.lo parseln.lo progname.lo rcstr.lo \
roundup.lo secure_path.lo setgroups.lo strsplit.lo strtobool.lo \
strtoid.lo strtomode.lo strtonum.lo sudo_conf.lo \
logfac.lo logpri.lo mkdir_parents.lo parseln.lo progname.lo rcstr.lo \
regex.lo roundup.lo secure_path.lo setgroups.lo strsplit.lo \
strtobool.lo strtoid.lo strtomode.lo strtonum.lo sudo_conf.lo \
sudo_debug.lo sudo_dso.lo term.lo ttyname_dev.lo \
ttysize.lo uuid.lo @COMMON_OBJS@ @LTLIBOBJS@
@@ -1212,6 +1212,16 @@ reallocarray.i: $(srcdir)/reallocarray.c $(incdir)/sudo_compat.h \
$(CC) -E -o $@ $(CPPFLAGS) $<
reallocarray.plog: reallocarray.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/reallocarray.c --i-file $< --output-file $@
regex.lo: $(srcdir)/regex.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regex.c
regex.i: $(srcdir)/regex.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
regex.plog: regex.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regex.c --i-file $< --output-file $@
roundup.lo: $(srcdir)/roundup.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \

131
lib/util/regex.c Normal file
View File

@@ -0,0 +1,131 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include <config.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include "sudo_compat.h"
#include "sudo_debug.h"
#include "sudo_util.h"
#include "sudo_gettext.h"
static char errbuf[1024];
/*
* Like strdup but collapses repeated '?', '*' and '+' ops in a regex.
* Glibc regcomp() has a bug where it uses excessive memory for repeated
* '+' ops. Collapse them to avoid running the fuzzer out of memory.
*/
static char *
dup_pattern(const char *src)
{
char *dst, *ret;
char ch, prev = '\0';
size_t len;
debug_decl(dup_pattern, SUDO_DEBUG_UTIL);
len = strlen(src);
ret = malloc(len + 1);
if (ret == NULL)
debug_return_ptr(NULL);
dst = ret;
while ((ch = *src++) != '\0') {
switch (ch) {
case '\\':
if (*src != '\0') {
*dst++ = '\\';
*dst++ = *src++;
prev = '\0';
continue;
}
break;
case '?':
case '*':
case '+':
if (ch == prev) {
continue;
}
break;
}
*dst++ = ch;
prev = ch;
}
*dst = '\0';
debug_return_ptr(ret);
}
/*
* Wrapper around regcomp() that handles a regex starting with (?i).
* Avoid using regex_t in the function args so we don't need to
* include regex.h everywhere.
*/
bool
sudo_regex_compile_v1(void *v, const char *pattern, const char **errstr)
{
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
regex_t *preg;
char *copy = NULL;
const char *cp;
regex_t rebuf;
debug_decl(regex_compile, SUDO_DEBUG_UTIL);
/* Some callers just want to check the validity of the pattern. */
preg = v ? v : &rebuf;
/* Check for (?i) to enable case-insensitive matching. */
cp = pattern[0] == '^' ? pattern + 1 : pattern;
if (strncmp(cp, "(?i)", 4) == 0) {
cflags |= REG_ICASE;
copy = dup_pattern(pattern + 4);
if (copy == NULL) {
*errstr = N_("unable to allocate memory");
debug_return_bool(false);
}
if (pattern[0] == '^')
copy[0] = '^';
} else {
copy = dup_pattern(pattern);
if (copy == NULL) {
*errstr = N_("unable to allocate memory");
debug_return_bool(false);
}
}
errcode = regcomp(preg, copy, cflags);
if (errcode == 0) {
if (preg == &rebuf)
regfree(&rebuf);
} else {
regerror(errcode, preg, errbuf, sizeof(errbuf));
*errstr = errbuf;
}
free(copy);
debug_return_bool(errcode == 0);
}

View File

@@ -119,6 +119,7 @@ sudo_rcstr_addref
sudo_rcstr_alloc
sudo_rcstr_delref
sudo_rcstr_dup
sudo_regex_compile_v1
sudo_secure_dir_v1
sudo_secure_file_v1
sudo_setgroups_v1

View File

@@ -36,7 +36,6 @@
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <regex.h>
#include <syslog.h>
#include "sudoers.h"
@@ -1287,22 +1286,17 @@ bool
cb_passprompt_regex(const union sudo_defs_val *sd_un, int op)
{
struct list_member *lm;
int errcode;
char errbuf[1024];
regex_t re;
const char *errstr;
debug_decl(cb_passprompt_regex, SUDOERS_DEBUG_DEFAULTS);
/* If adding one or more regexps, validate them with regcomp(). */
/* If adding one or more regexps, make sure they are valid. */
if (op == '+' || op == true) {
SLIST_FOREACH(lm, &sd_un->list, entries) {
errcode = regcomp(&re, lm->value, REG_EXTENDED|REG_NOSUB);
if (errcode != 0) {
regerror(errcode, &re, errbuf, sizeof(errbuf));
if (!sudo_regex_compile(NULL, lm->value, &errstr)) {
sudo_warnx(U_("invalid regular expression \"%s\": %s"),
lm->value, errbuf);
lm->value, U_(errstr));
debug_return_bool(false);
}
regfree(&re);
}
}

View File

@@ -60,36 +60,20 @@
static bool
regex_matches(const char *pattern, const char *str)
{
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
char *copy = NULL;
const char *errstr;
int errcode;
regex_t re;
debug_decl(regex_matches, SUDOERS_DEBUG_MATCH);
/* Check for (?i) to enable case-insensitive matching. */
if (strncmp(pattern, "^(?i)", 5) == 0) {
cflags |= REG_ICASE;
copy = strdup(pattern + 4);
if (copy == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_bool(false);
}
copy[0] = '^';
pattern = copy;
}
errcode = regcomp(&re, pattern, cflags);
if (errcode == 0) {
errcode = regexec(&re, str, 0, NULL, 0);
regfree(&re);
} else {
char errbuf[1024];
regerror(errcode, &re, errbuf, sizeof(errbuf));
if (!sudo_regex_compile(&re, pattern, &errstr)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to compile regular expression \"%s\": %s",
pattern, errbuf);
pattern, errstr);
debug_return_bool(false);
}
free(copy);
errcode = regexec(&re, str, 0, NULL, 0);
regfree(&re);
debug_return_bool(errcode == 0);
}

View File

@@ -1,3 +1,6 @@
# Test passprompt_regex
Defaults passprompt_regex="(?i)password: *"
# Test simple command with regex args
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$

View File

@@ -1,4 +1,17 @@
{
"Defaults": [
{
"Options": [
{
"operation": "list_assign",
"passprompt_regex": [
"(?i)password:",
"*"
]
}
]
}
],
"User_Specs": [
{
"User_List": [

View File

@@ -1,3 +1,10 @@
dn: cn=defaults,ou=SUDOers,dc=sudo,dc=ws
objectClass: top
objectClass: sudoRole
cn: defaults
description: Default sudoOption's go here
sudoOption: passprompt_regex=(?i)password: *
dn: cn=user,ou=SUDOers,dc=sudo,dc=ws
objectClass: top
objectClass: sudoRole

View File

@@ -1,3 +1,5 @@
Defaults passprompt_regex="(?i)password: *"
# sudoRole user, user_1, user_2, user_3, user_4, user_5, user_6, user_7,
# user_8, user_9, user_10, user_11
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$, /usr/bin/c*\

View File

@@ -1,5 +1,7 @@
Parses OK
Defaults passprompt_regex="(?i)password: *"
user ALL = /bin/ls ^/etc/(hosts|motd|issue)$
user ALL = /usr/bin/c* ^/etc/(hosts|motd|issue)$
user ALL = ^/usr/bin/(who|w|id|whoami)$

View File

@@ -1,3 +1,6 @@
#
DEFAULTS DEFVAR = BEGINSTR STRBODY ENDSTR WORD(4)
#
WORD(6) ALL = COMMAND ARG REGEX

View File

@@ -1195,6 +1195,7 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
bool or = false, not = false;
struct search_node *sn;
char type, **av;
const char *errstr;
debug_decl(parse_expr, SUDO_DEBUG_UTIL);
for (av = argv; *av != NULL; av++) {
@@ -1288,8 +1289,10 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
if (*(++av) == NULL)
sudo_fatalx(U_("%s requires an argument"), av[-1]);
if (type == ST_PATTERN) {
if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0)
sudo_fatalx(U_("invalid regular expression: %s"), *av);
if (!sudo_regex_compile(&sn->u.cmdre, *av, &errstr)) {
sudo_fatalx(U_("invalid regular expression \"%s\": %s"),
*av, U_(errstr));
}
} else if (type == ST_TODATE || type == ST_FROMDATE) {
sn->u.tstamp.tv_sec = get_date(*av);
sn->u.tstamp.tv_nsec = 0;
@@ -1542,6 +1545,7 @@ list_sessions(int argc, char **argv, const char *pattern, const char *user,
const char *tty)
{
regex_t rebuf, *re = NULL;
const char *errstr;
debug_decl(list_sessions, SUDO_DEBUG_UTIL);
/* Parse search expression if present */
@@ -1550,8 +1554,10 @@ list_sessions(int argc, char **argv, const char *pattern, const char *user,
/* optional regex */
if (pattern) {
re = &rebuf;
if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
sudo_fatalx(U_("invalid regular expression: %s"), pattern);
if (!sudo_regex_compile(re, pattern, &errstr)) {
sudo_fatalx(U_("invalid regular expression \"%s\": %s"),
pattern, U_(errstr));
}
}
debug_return_int(find_sessions(session_dir, re, user, tty));

View File

@@ -3083,7 +3083,7 @@ char *sudoerstext;
int sudolineno; /* current sudoers line number. */
char *sudoers; /* sudoers file being parsed. */
char *sudoers_errstr; /* description of last error from lexer. */
const char *sudoers_errstr; /* description of last error from lexer. */
struct sudolinebuf sudolinebuf; /* sudoers line being parsed. */
/* Default sudoers path, mode and owner (may be set via sudo.conf) */
@@ -3648,7 +3648,7 @@ YY_RULE_SETUP
BEGIN INITIAL;
continued = false;
if (sudoers_strict) {
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
if (!sudo_regex_compile(NULL, sudoerstext, &sudoers_errstr)) {
LEXTRACE("ERROR ");
return ERROR;
}
@@ -4283,7 +4283,7 @@ YY_RULE_SETUP
#line 774 "toke.l"
{
if (sudoers_strict) {
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
if (!sudo_regex_compile(NULL, sudoerstext, &sudoers_errstr)) {
LEXTRACE("ERROR ");
return ERROR;
}

View File

@@ -27,7 +27,7 @@ struct sudolinebuf {
size_t toke_start; /* starting column of current token */
size_t toke_end; /* ending column of current token */
};
extern char *sudoers_errstr;
extern const char *sudoers_errstr;
extern struct sudolinebuf sudolinebuf;
bool append(const char *, size_t);
@@ -35,7 +35,6 @@ bool fill_args(const char *, size_t, int);
bool fill_cmnd(const char *, size_t);
bool fill(const char *, size_t);
bool ipv6_valid(const char *s);
bool regex_valid(const char *pattern, char **errstr);
int sudoers_trace_print(const char *);
void sudoerserrorf(const char *, ...) __printf0like(1, 2);
void sudoerserror(const char *);

View File

@@ -55,7 +55,7 @@
int sudolineno; /* current sudoers line number. */
char *sudoers; /* sudoers file being parsed. */
char *sudoers_errstr; /* description of last error from lexer. */
const char *sudoers_errstr; /* description of last error from lexer. */
struct sudolinebuf sudolinebuf; /* sudoers line being parsed. */
/* Default sudoers path, mode and owner (may be set via sudo.conf) */
@@ -297,7 +297,7 @@ DEFVAR [a-z_]+
BEGIN INITIAL;
continued = false;
if (sudoers_strict) {
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
if (!sudo_regex_compile(NULL, sudoerstext, &sudoers_errstr)) {
LEXTRACE("ERROR ");
return ERROR;
}
@@ -773,7 +773,7 @@ sudoedit {
{REGEX} {
if (sudoers_strict) {
if (!regex_valid(sudoerstext, &sudoers_errstr)) {
if (!sudo_regex_compile(NULL, sudoerstext, &sudoers_errstr)) {
LEXTRACE("ERROR ");
return ERROR;
}

View File

@@ -39,7 +39,6 @@
static unsigned int arg_len = 0;
static unsigned int arg_size = 0;
static char errbuf[1024];
/*
* Copy the string and collapse any escaped characters.
@@ -247,36 +246,3 @@ ipv6_valid(const char *s)
debug_return_bool(nmatch <= 1);
}
bool
regex_valid(const char *pattern, char **errstr)
{
int errcode, cflags = REG_EXTENDED|REG_NOSUB;
char *copy = NULL;
regex_t re;
debug_decl(regex_valid, SUDOERS_DEBUG_PARSER);
/* Check for (?i) to enable case-insensitive matching. */
if (strncmp(pattern, "^(?i)", 5) == 0) {
cflags |= REG_ICASE;
copy = strdup(pattern + 4);
if (copy == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
sudoerserror(NULL);
debug_return_bool(false);
}
copy[0] = '^';
pattern = copy;
}
errcode = regcomp(&re, pattern, cflags);
if (errcode == 0) {
regfree(&re);
} else {
regerror(errcode, &re, errbuf, sizeof(errbuf));
*errstr = errbuf;
}
free(copy);
debug_return_bool(errcode == 0);
}