Merge pull request #2992 from kientzle/kientzle-acl-isint-overflow
Expand ACL testing
(cherry picked from commit b18956ea60)
This commit is contained in:
1 parent
d6e8e98886
commit
3741a47683
5 files changed
+1004
-17
No files matched your search
@@ -378,10 +378,12 @@ libarchive_test_SOURCES= \
|
||||
libarchive/test/test_7zip_filename_encoding.c \
|
||||
libarchive/test/test_acl_nfs4.c \
|
||||
libarchive/test/test_acl_nfs4_null_id_overflow.c \
|
||||
libarchive/test/test_acl_nfs4_text.c \
|
||||
libarchive/test/test_acl_pax.c \
|
||||
libarchive/test/test_acl_platform_nfs4.c \
|
||||
libarchive/test/test_acl_platform_posix1e.c \
|
||||
libarchive/test/test_acl_posix1e.c \
|
||||
libarchive/test/test_acl_posix1e_text.c \
|
||||
libarchive/test/test_acl_text.c \
|
||||
libarchive/test/test_ar_mode.c \
|
||||
libarchive/test/test_archive_api_feature.c \
|
||||
|
||||
+44
-17
@@ -1250,11 +1250,18 @@ archive_acl_from_text_w(struct archive_acl *acl, const wchar_t *text,
|
||||
type = want_type;
|
||||
|
||||
/* Check for a numeric ID in field n+1 or n+3. */
|
||||
isint_w(field[n + 1].start, field[n + 1].end, &id);
|
||||
if (isint_w(field[n + 1].start, field[n + 1].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
/* Field n+3 is optional. */
|
||||
if (id == -1 && fields > n+3)
|
||||
isint_w(field[n + 3].start, field[n + 3].end,
|
||||
&id);
|
||||
if (id == -1 && fields > n+3 &&
|
||||
isint_w(field[n + 3].start, field[n + 3].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
|
||||
tag = 0;
|
||||
s = field[n].start;
|
||||
@@ -1369,7 +1376,10 @@ archive_acl_from_text_w(struct archive_acl *acl, const wchar_t *text,
|
||||
tag == ARCHIVE_ENTRY_ACL_GROUP) {
|
||||
n = 1;
|
||||
name = field[1];
|
||||
isint_w(name.start, name.end, &id);
|
||||
if (isint_w(name.start, name.end, &id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
n = 0;
|
||||
|
||||
@@ -1404,7 +1414,11 @@ archive_acl_from_text_w(struct archive_acl *acl, const wchar_t *text,
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
isint_w(field[4 + n].start, field[4 + n].end, &id);
|
||||
if (isint_w(field[4 + n].start, field[4 + n].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add entry to the internal list. */
|
||||
@@ -1438,8 +1452,8 @@ isint_w(const wchar_t *start, const wchar_t *end, int *result)
|
||||
if (*start < L'0' || *start > L'9')
|
||||
return (0);
|
||||
if (n > (INT_MAX / 10) ||
|
||||
(n == INT_MAX / 10 && (*start - L'0') > INT_MAX % 10)) {
|
||||
n = INT_MAX;
|
||||
(n == INT_MAX / 10 && (*start - L'0') >= INT_MAX % 10)) {
|
||||
return (-1);
|
||||
} else {
|
||||
n *= 10;
|
||||
n += *start - L'0';
|
||||
@@ -1749,11 +1763,18 @@ archive_acl_from_text_nl(struct archive_acl *acl, const char *text,
|
||||
type = want_type;
|
||||
|
||||
/* Check for a numeric ID in field n+1 or n+3. */
|
||||
isint(field[n + 1].start, field[n + 1].end, &id);
|
||||
if (isint(field[n + 1].start, field[n + 1].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
/* Field n+3 is optional. */
|
||||
if (id == -1 && fields > (n + 3))
|
||||
isint(field[n + 3].start, field[n + 3].end,
|
||||
&id);
|
||||
if (id == -1 && fields > (n + 3) &&
|
||||
isint(field[n + 3].start, field[n + 3].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
|
||||
tag = 0;
|
||||
s = field[n].start;
|
||||
@@ -1871,7 +1892,10 @@ archive_acl_from_text_nl(struct archive_acl *acl, const char *text,
|
||||
tag == ARCHIVE_ENTRY_ACL_GROUP) {
|
||||
n = 1;
|
||||
name = field[1];
|
||||
isint(name.start, name.end, &id);
|
||||
if (isint(name.start, name.end, &id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
n = 0;
|
||||
|
||||
@@ -1906,8 +1930,11 @@ archive_acl_from_text_nl(struct archive_acl *acl, const char *text,
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
isint(field[4 + n].start, field[4 + n].end,
|
||||
&id);
|
||||
if (isint(field[4 + n].start, field[4 + n].end,
|
||||
&id) < 0) {
|
||||
ret = ARCHIVE_WARN;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add entry to the internal list. */
|
||||
@@ -1941,8 +1968,8 @@ isint(const char *start, const char *end, int *result)
|
||||
if (*start < '0' || *start > '9')
|
||||
return (0);
|
||||
if (n > (INT_MAX / 10) ||
|
||||
(n == INT_MAX / 10 && (*start - '0') > INT_MAX % 10)) {
|
||||
n = INT_MAX;
|
||||
(n == INT_MAX / 10 && (*start - '0') >= INT_MAX % 10)) {
|
||||
return (-1);
|
||||
} else {
|
||||
n *= 10;
|
||||
n += *start - '0';
|
||||
|
||||
@@ -12,10 +12,12 @@ IF(ENABLE_TEST)
|
||||
test_7zip_filename_encoding.c
|
||||
test_acl_nfs4.c
|
||||
test_acl_nfs4_null_id_overflow.c
|
||||
test_acl_nfs4_text.c
|
||||
test_acl_pax.c
|
||||
test_acl_platform_nfs4.c
|
||||
test_acl_platform_posix1e.c
|
||||
test_acl_posix1e.c
|
||||
test_acl_posix1e_text.c
|
||||
test_acl_text.c
|
||||
test_ar_mode.c
|
||||
test_archive_api_feature.c
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
/*-
|
||||
* Copyright (c) 2026 Tim Kientzle
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "test.h"
|
||||
#include <limits.h>
|
||||
|
||||
/*
|
||||
* Focused tests for NFSv4 ACL text parsing and serialization.
|
||||
*
|
||||
* test_acl_text.c already covers basic round-trips with well-formed input.
|
||||
* These tests probe areas not reached there: all four ACL types, numeric IDs,
|
||||
* extra-ID field override, isint() overflow, malformed entries, compact mode,
|
||||
* and the wide-char path.
|
||||
*/
|
||||
|
||||
/* NFSv4 text in standard (no extra ID), extra-ID, and compact+extra-ID forms.
|
||||
* These are the same strings used in test_acl_text.c acltext[9..11]. */
|
||||
static const char *nfs4_std =
|
||||
"user:user77:rw-p--a-R-c-o-:-------:allow\n"
|
||||
"user:user101:-w-pdD--------:fdin---:deny\n"
|
||||
"group:group78:r-----a-R-c---:------I:allow\n"
|
||||
"owner@:rwxp--aARWcCo-:-------:allow\n"
|
||||
"group@:rw-p--a-R-c---:-------:allow\n"
|
||||
"everyone@:r-----a-R-c--s:-------:allow";
|
||||
|
||||
static const char *nfs4_extra_id =
|
||||
"user:user77:rw-p--a-R-c-o-:-------:allow:77\n"
|
||||
"user:user101:-w-pdD--------:fdin---:deny:101\n"
|
||||
"group:group78:r-----a-R-c---:------I:allow:78\n"
|
||||
"owner@:rwxp--aARWcCo-:-------:allow\n"
|
||||
"group@:rw-p--a-R-c---:-------:allow\n"
|
||||
"everyone@:r-----a-R-c--s:-------:allow";
|
||||
|
||||
static const char *nfs4_compact =
|
||||
"user:user77:rwpaRco::allow:77\n"
|
||||
"user:user101:wpdD:fdin:deny:101\n"
|
||||
"group:group78:raRc:I:allow:78\n"
|
||||
"owner@:rwxpaARWcCo::allow\n"
|
||||
"group@:rwpaRc::allow\n"
|
||||
"everyone@:raRcs::allow";
|
||||
|
||||
static wchar_t *
|
||||
s_to_ws(const char *s)
|
||||
{
|
||||
size_t len = strlen(s) + 1;
|
||||
wchar_t *ws = malloc(len * sizeof(wchar_t));
|
||||
assert(ws != NULL);
|
||||
assert(mbstowcs(ws, s, len) != (size_t)-1);
|
||||
return (ws);
|
||||
}
|
||||
|
||||
/*
|
||||
* Round-trip: parse NFS4 text → serialize → compare.
|
||||
* Covers standard, extra-ID, and compact+extra-ID formats,
|
||||
* for both the narrow and wide serializers.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_nfs4_text_roundtrip)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
wchar_t *wtext, *ws;
|
||||
ssize_t len;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0644);
|
||||
|
||||
/* Standard format */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, nfs4_std,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4);
|
||||
assertEqualString(nfs4_std, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Extra-ID format: trailing :id must be preserved */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, nfs4_extra_id,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4 | ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID);
|
||||
assertEqualString(nfs4_extra_id, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Compact format: empty perm/flag fields must round-trip */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, nfs4_compact,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4 |
|
||||
ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID |
|
||||
ARCHIVE_ENTRY_ACL_STYLE_COMPACT);
|
||||
assertEqualString(nfs4_compact, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Wide: standard format */
|
||||
ws = s_to_ws(nfs4_std);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text_w(ae, ws,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
wtext = archive_entry_acl_to_text_w(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4);
|
||||
assertEqualWString(ws, wtext);
|
||||
free(wtext);
|
||||
free(ws);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Wide: compact+extra-ID format */
|
||||
ws = s_to_ws(nfs4_compact);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text_w(ae, ws,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
wtext = archive_entry_acl_to_text_w(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4 |
|
||||
ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID |
|
||||
ARCHIVE_ENTRY_ACL_STYLE_COMPACT);
|
||||
assertEqualWString(ws, wtext);
|
||||
free(wtext);
|
||||
free(ws);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* All four ACL entry types: allow, deny, audit, alarm.
|
||||
* test_acl_text.c only exercises allow and deny.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_nfs4_text_types)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
wchar_t *wtext, *ws;
|
||||
ssize_t len;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
static const char *audit_alarm_text =
|
||||
"user:alice:rw-p--aARWcCos:-------:audit\n"
|
||||
"group:staff:rw-p--aARWcCos:-------:alarm";
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
|
||||
/* Parse and verify both audit and alarm entries are stored */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, audit_alarm_text,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(2,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_AUDIT, type);
|
||||
assertEqualString("alice", name);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ALARM, type);
|
||||
assertEqualString("staff", name);
|
||||
|
||||
/* Round-trip: serialize then compare */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4);
|
||||
assertEqualString(audit_alarm_text, text);
|
||||
|
||||
/* Verify same round-trip via wide path */
|
||||
ws = s_to_ws(audit_alarm_text);
|
||||
wtext = archive_entry_acl_to_text_w(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4);
|
||||
assertEqualWString(ws, wtext);
|
||||
free(ws);
|
||||
free(wtext);
|
||||
free(text);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* Numeric IDs in the name field and the extra-ID override field.
|
||||
*
|
||||
* 1. "user:1000:..." — the name field is a pure number; id is set from it.
|
||||
* 2. "user:name:...:42" — the trailing id field overrides any id derived
|
||||
* from the name.
|
||||
* 3. "user:1000:...:42" — trailing id overrides even a numeric name.
|
||||
* 4. isint() overflow: a field with more than 10 digits is clamped to INT_MAX.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_nfs4_text_numeric_id)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
ssize_t len;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
|
||||
/* 1. Numeric-only name → id derived from name field */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:1000:rw-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(1,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(1000, qual);
|
||||
assertEqualString("1000", name);
|
||||
/* Without EXTRA_ID the id is carried by the name string, not :id */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4);
|
||||
assertEqualString("user:1000:rw-p--a-R-c-o-:-------:allow", text);
|
||||
free(text);
|
||||
/* With EXTRA_ID the id is appended */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4 | ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID);
|
||||
assertEqualString("user:1000:rw-p--a-R-c-o-:-------:allow:1000", text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 2. Trailing extra-ID field overrides name-derived id */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:user77:rw-p--a-R-c-o-:-------:allow:42",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(1,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(42, qual);
|
||||
assertEqualString("user77", name);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 3. Trailing extra-ID also overrides a numeric name */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:1000:rw-p--a-R-c-o-:-------:allow:42",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(1,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(42, qual);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 4. isint() overflow: extra-ID field too large → entry rejected */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rw-p--a-R-c-o-:-------:allow:99999999999",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 5. INT_MAX (2147483647) in the extra-ID field → also rejected */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rw-p--a-R-c-o-:-------:allow:2147483647",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 6. INT_MAX - 1 (2147483646) is the largest accepted id */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rw-p--a-R-c-o-:-------:allow:2147483646",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(1,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(2147483646, qual);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* 7. Overflow in the name field (numeric name too large) → rejected */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:99999999999:rw-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* Malformed NFS4 ACL entries — each should return ARCHIVE_WARN without
|
||||
* crashing. Also verifies that valid entries before an invalid one are
|
||||
* still stored.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_nfs4_text_invalid)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
|
||||
/* Unknown tag */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"bogus:name:rw-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Unknown type string */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rw-p--a-R-c-o-:-------:bogus",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Invalid character in perms field */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rq-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Invalid character in flags field */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:name:rw-p--a-R-c-o-:q------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Just a tag with no colon-separated fields */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae, "user",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Valid entry followed by an invalid one: valid entry must be stored
|
||||
* and ARCHIVE_WARN returned (not ARCHIVE_FATAL). */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:alice:rw-p--a-R-c-o-:-------:allow\n"
|
||||
"bogus:bob:rw-p--a-R-c-o-:-------:deny",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(1,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ALLOW, type);
|
||||
assertEqualString("alice", name);
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
/* Same tests via the wide parser */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"bogus:name:rw-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"user:name:rw-p--a-R-c-o-:-------:bogus",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"user:name:rq-p--a-R-c-o-:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"user:name:rw-p--a-R-c-o-:q------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_NFS4));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
/*-
|
||||
* Copyright (c) 2026 Tim Kientzle
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "test.h"
|
||||
#include <limits.h>
|
||||
|
||||
/*
|
||||
* Focused tests for POSIX.1e ACL text parsing and serialization.
|
||||
*
|
||||
* test_acl_text.c covers basic POSIX.1e round-trips but does not exercise:
|
||||
* the mask entry, the Solaris two-field format for mask and other, uppercase
|
||||
* mode letters (R/W/X), isint() overflow on the POSIX path, and various
|
||||
* invalid-input error paths. These tests fill those gaps.
|
||||
*
|
||||
* Note: archive_entry_acl_reset(ae, TYPE_ACCESS) always returns the count
|
||||
* of extended ACL entries PLUS 3 synthesized base entries (USER_OBJ,
|
||||
* GROUP_OBJ, OTHER) derived from the mode bits. Tests below account for
|
||||
* this by adding 3 to the expected count and skipping those 3 entries
|
||||
* before iterating over the named/mask entries of interest.
|
||||
*/
|
||||
|
||||
/* ACCESS-only ACL including a mask entry. */
|
||||
static const char *posix1e_access =
|
||||
"user::rwx\n"
|
||||
"group::r-x\n"
|
||||
"other::r-x\n"
|
||||
"user:alice:r-x\n"
|
||||
"group:staff:rwx\n"
|
||||
"mask::r-x";
|
||||
|
||||
/* Combined ACCESS + DEFAULT ACL, with mask in both sections. */
|
||||
static const char *posix1e_full =
|
||||
"user::rwx\n"
|
||||
"group::r-x\n"
|
||||
"other::r-x\n"
|
||||
"user:alice:r-x\n"
|
||||
"group:staff:rwx\n"
|
||||
"mask::r-x\n"
|
||||
"default:user::r-x\n"
|
||||
"default:group::r-x\n"
|
||||
"default:other::---\n"
|
||||
"default:user:alice:r-x\n"
|
||||
"default:group:staff:--x\n"
|
||||
"default:mask::r-x";
|
||||
|
||||
/* posix1e_full with numeric IDs appended to named user/group entries. */
|
||||
static const char *posix1e_extra_id =
|
||||
"user::rwx\n"
|
||||
"group::r-x\n"
|
||||
"other::r-x\n"
|
||||
"user:alice:r-x:77\n"
|
||||
"group:staff:rwx:78\n"
|
||||
"mask::r-x\n"
|
||||
"default:user::r-x\n"
|
||||
"default:group::r-x\n"
|
||||
"default:other::---\n"
|
||||
"default:user:alice:r-x:77\n"
|
||||
"default:group:staff:--x:78\n"
|
||||
"default:mask::r-x";
|
||||
|
||||
/*
|
||||
* Solaris-style ACCESS ACL: mask and other use one colon (no empty name
|
||||
* field between tag and permission string).
|
||||
*/
|
||||
static const char *posix1e_solaris =
|
||||
"user::rwx\n"
|
||||
"group::r-x\n"
|
||||
"other:r-x\n"
|
||||
"user:alice:r-x\n"
|
||||
"mask:r-x";
|
||||
|
||||
static wchar_t *
|
||||
s_to_ws(const char *s)
|
||||
{
|
||||
size_t len = strlen(s) + 1;
|
||||
wchar_t *ws = malloc(len * sizeof(wchar_t));
|
||||
assert(ws != NULL);
|
||||
assert(mbstowcs(ws, s, len) != (size_t)-1);
|
||||
return (ws);
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip the three base entries (USER_OBJ, GROUP_OBJ, OTHER) that
|
||||
* archive_entry_acl_reset() synthesises from mode bits and that
|
||||
* archive_entry_acl_next() always returns first for ACCESS ACLs.
|
||||
*/
|
||||
static void
|
||||
skip_base_entries(struct archive_entry *ae)
|
||||
{
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Round-trip: parse POSIX.1e text → serialize → compare.
|
||||
* Covers ACCESS-only, combined ACCESS+DEFAULT, and the EXTRA_ID style,
|
||||
* for both the narrow and wide serializers.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_posix1e_text_roundtrip)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
wchar_t *wtext, *ws;
|
||||
ssize_t len;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* ACCESS-only with mask entry */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, posix1e_access,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
|
||||
assertEqualString(posix1e_access, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Combined ACCESS + DEFAULT — serializer auto-adds MARK_DEFAULT */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, posix1e_full,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_POSIX1E));
|
||||
text = archive_entry_acl_to_text(ae, &len, 0);
|
||||
assertEqualString(posix1e_full, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* EXTRA_ID format */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, posix1e_extra_id,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_POSIX1E));
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID);
|
||||
assertEqualString(posix1e_extra_id, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Wide: ACCESS-only round-trip */
|
||||
ws = s_to_ws(posix1e_access);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text_w(ae, ws,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
wtext = archive_entry_acl_to_text_w(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
|
||||
assertEqualWString(ws, wtext);
|
||||
free(wtext);
|
||||
free(ws);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Wide: combined ACCESS + DEFAULT */
|
||||
ws = s_to_ws(posix1e_full);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text_w(ae, ws,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_POSIX1E));
|
||||
wtext = archive_entry_acl_to_text_w(ae, &len, 0);
|
||||
assertEqualWString(ws, wtext);
|
||||
free(wtext);
|
||||
free(ws);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* Solaris two-field format: "mask:rwx" and "other:rwx" omit the empty
|
||||
* name field, so each has only two colon-separated tokens instead of three.
|
||||
* The parser recognises this form; the serializer reproduces it when
|
||||
* ARCHIVE_ENTRY_ACL_STYLE_SOLARIS is set.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_posix1e_text_solaris)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
ssize_t len;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/*
|
||||
* Parse Solaris-format text. Extended entries are: named user alice
|
||||
* and mask — 2 extended + 3 base = 5 total.
|
||||
*/
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae, posix1e_solaris,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(5,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
|
||||
assertEqualString("alice", name);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_MASK, tag);
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_READ | ARCHIVE_ENTRY_ACL_EXECUTE,
|
||||
permset);
|
||||
|
||||
/* Serialize with STYLE_SOLARIS and compare */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS | ARCHIVE_ENTRY_ACL_STYLE_SOLARIS);
|
||||
assertEqualString(posix1e_solaris, text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Solaris style with non-empty name on other field → ARCHIVE_WARN */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother:nobody:r-x\n",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* ismode() accepts uppercase R, W, X as well as lowercase; the serializer
|
||||
* always emits lowercase. Both narrow and wide parsers share this
|
||||
* behaviour.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_posix1e_text_ismode)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
ssize_t len;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/*
|
||||
* Uppercase R/W/X treated identically to r/w/x.
|
||||
* 1 extended entry (alice) + 3 base = 4 total.
|
||||
*/
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::RWX\ngroup::R-X\nother::---\nuser:alice:RwX",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
|
||||
assertEqualString("alice", name);
|
||||
assertEqualInt(
|
||||
ARCHIVE_ENTRY_ACL_READ | ARCHIVE_ENTRY_ACL_WRITE |
|
||||
ARCHIVE_ENTRY_ACL_EXECUTE, permset);
|
||||
|
||||
/* Serializer always emits lowercase */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
|
||||
assertEqualString(
|
||||
"user::rwx\ngroup::r-x\nother::---\nuser:alice:rwx", text);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Same via the wide parser */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"user::RWX\ngroup::R-X\nother::---\nuser:alice:RwX",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(
|
||||
ARCHIVE_ENTRY_ACL_READ | ARCHIVE_ENTRY_ACL_WRITE |
|
||||
ARCHIVE_ENTRY_ACL_EXECUTE, permset);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* Numeric IDs in the name field and the extra-ID override field,
|
||||
* including the isint() overflow boundary now enforced on the POSIX path.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_posix1e_text_numeric_id)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
char *text;
|
||||
ssize_t len;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 1. Numeric-only name → id derived from name field */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:1000:rwx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
/* 1 extended + 3 base = 4 */
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(1000, qual);
|
||||
assertEqualString("1000", name);
|
||||
/* Without EXTRA_ID the id is carried by the name string */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
|
||||
assert(strstr(text, "user:1000:rwx") != NULL);
|
||||
free(text);
|
||||
/* With EXTRA_ID the id is also appended */
|
||||
text = archive_entry_acl_to_text(ae, &len,
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS | ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID);
|
||||
assert(strstr(text, "user:1000:rwx:1000") != NULL);
|
||||
free(text);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 2. Trailing extra-ID field overrides name-derived id */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:alice:r-x:42",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(42, qual);
|
||||
assertEqualString("alice", name);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 3. Overflow in extra-ID field → extended entry rejected, no ext entries */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:alice:r-x:99999999999",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 4. INT_MAX (2147483647) in the extra-ID field → also rejected */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:alice:r-x:2147483647",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 5. INT_MAX - 1 (2147483646) is the largest accepted id */
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:alice:r-x:2147483646",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(2147483646, qual);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* 6. Overflow in the name field (all-digit name too large) → rejected */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\nuser:99999999999:r-x",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
|
||||
/*
|
||||
* Malformed POSIX.1e ACL entries — each should return ARCHIVE_WARN without
|
||||
* crashing and without storing invalid data.
|
||||
*/
|
||||
DEFINE_TEST(test_acl_posix1e_text_invalid)
|
||||
{
|
||||
struct archive_entry *ae;
|
||||
int type, permset, tag, qual;
|
||||
const char *name;
|
||||
|
||||
ae = archive_entry_new();
|
||||
assert(ae != NULL);
|
||||
archive_entry_set_pathname(ae, "file");
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Invalid character in permission field */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user:alice:rqx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Unknown tag */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"bogus:alice:rwx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* NFSv4 "everyone@" tag is not recognised by the POSIX parser */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"everyone@:r-----a-R-c--s:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Bare tag with no colon-separated fields */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae, "user",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/*
|
||||
* Valid entry followed by invalid — valid entry must be stored.
|
||||
* 1 extended (alice) + 3 base = 4 total.
|
||||
*/
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text(ae,
|
||||
"user::rwx\ngroup::r-x\nother::r-x\n"
|
||||
"user:alice:r-x\n"
|
||||
"bogus:bob:rwx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(4,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
skip_base_entries(ae);
|
||||
assertEqualInt(ARCHIVE_OK,
|
||||
archive_entry_acl_next(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
|
||||
&type, &permset, &tag, &qual, &name));
|
||||
assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
|
||||
assertEqualString("alice", name);
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Wide parser: invalid permission character */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"user:alice:rqx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Wide parser: unknown tag */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"bogus:alice:rwx",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
archive_entry_acl_clear(ae);
|
||||
archive_entry_set_mode(ae, AE_IFREG | 0755);
|
||||
|
||||
/* Wide parser: NFSv4 tag rejected by POSIX parser */
|
||||
assertEqualInt(ARCHIVE_WARN,
|
||||
archive_entry_acl_from_text_w(ae,
|
||||
L"everyone@:r-----a-R-c--s:-------:allow",
|
||||
ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
assertEqualInt(0,
|
||||
archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
|
||||
|
||||
archive_entry_free(ae);
|
||||
}
|
||||
Reference in new issue
Block a user