From: Tim Kientzle Date: Sat, 2 May 2026 18:51:47 +0000 (-0700) Subject: [archive_acl] Reject ACL entries with out-of-range numeric IDs X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5ca297b7665fa76de6e4d310b8a97ec7d888ccdc;p=thirdparty%2Flibarchive.git [archive_acl] Reject ACL entries with out-of-range numeric IDs isint() and isint_w() previously clamped values >= INT_MAX to INT_MAX and returned success, allowing malformed ACL text to silently set IDs to an arbitrary sentinel value. Change them to return -1 (a new "overflow" indication) instead, and update all callers in both the NFS4 and POSIX parsers (narrow and wide) to treat overflow as ARCHIVE_WARN and skip the offending entry. Add test_acl_nfs4_text.c with four test functions covering NFS4 ACL text round-trips, audit/alarm entry types, numeric-ID handling including the overflow boundary (INT_MAX - 1 accepted, INT_MAX rejected), and malformed-entry error paths. --- diff --git a/Makefile.am b/Makefile.am index b19e6837c..72b64e7e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -378,6 +378,7 @@ 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 \ diff --git a/libarchive/archive_acl.c b/libarchive/archive_acl.c index af332eb20..ee165fcbd 100644 --- a/libarchive/archive_acl.c +++ b/libarchive/archive_acl.c @@ -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'; diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 2d2ff013f..8cd423932 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -12,6 +12,7 @@ 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 diff --git a/libarchive/test/test_acl_nfs4_text.c b/libarchive/test/test_acl_nfs4_text.c new file mode 100644 index 000000000..165d18ed8 --- /dev/null +++ b/libarchive/test/test_acl_nfs4_text.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 + +/* + * 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); +}