Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,14 @@ check_function_exists(fileno HAVE_FILENO)
check_function_exists(fseeko HAVE_FSEEKO)
check_function_exists(ftello HAVE_FTELLO)
check_function_exists(getprogname HAVE_GETPROGNAME)
check_function_exists(getxattr HAVE_GETXATTR)
check_function_exists(GetSecurityInfo HAVE_GETSECURITYINFO)
check_symbol_exists(localtime_r time.h HAVE_LOCALTIME_R)
check_symbol_exists(localtime_s time.h HAVE_LOCALTIME_S)
check_function_exists(memcpy_s HAVE_MEMCPY_S)
check_function_exists(random HAVE_RANDOM)
check_function_exists(setmode HAVE_SETMODE)
check_function_exists(setxattr HAVE_SETXATTR)
check_symbol_exists(snprintf stdio.h HAVE_SNPRINTF)
check_symbol_exists(snprintf_s stdio.h HAVE_SNPRINTF_S)
check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP)
Expand Down
2 changes: 2 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#cmakedefine HAVE_FSEEKO
#cmakedefine HAVE_FTELLO
#cmakedefine HAVE_GETPROGNAME
#cmakedefine HAVE_GETXATTR
#cmakedefine HAVE_GETSECURITYINFO
#cmakedefine HAVE_GNUTLS
#cmakedefine HAVE_LIBBZ2
Expand All @@ -44,6 +45,7 @@
#cmakedefine HAVE_MKSTEMP
#cmakedefine HAVE_OPENSSL
#cmakedefine HAVE_SETMODE
#cmakedefine HAVE_SETXATTR
#cmakedefine HAVE_SNPRINTF
#cmakedefine HAVE_SNPRINTF_S
#cmakedefine HAVE_STRCASECMP
Expand Down
38 changes: 37 additions & 1 deletion lib/zip_source_file_stdio_named.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#if defined(__linux__) && defined(HAVE_GETXATTR) && defined(HAVE_SETXATTR)
#define USE_ACL
#include <sys/xattr.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
Expand Down Expand Up @@ -391,7 +395,39 @@ static bool copy_permissions(zip_source_file_context_t *ctx) {
zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
return false;
}
/* TODO: copy ACLs */
#ifdef USE_ACL
static const char acl_name[] = "system.posix_acl_access";
void *acl_data;
ssize_t acl_size;

acl_size = getxattr(ctx->fname, acl_name, NULL, 0);
if (acl_size < 0) {
if (errno != ENODATA && errno != ENOTSUP && errno != EOPNOTSUPP) {
zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
return false;
}
}
else if (acl_size > 0) {
if ((acl_data = malloc((size_t)acl_size)) == NULL) {
zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
return false;
}
acl_size = getxattr(ctx->fname, acl_name, acl_data, (size_t)acl_size);
if (acl_size < 0) {
int saved_errno = errno;
free(acl_data);
zip_error_set(&ctx->error, ZIP_ER_RENAME, saved_errno);
return false;
}
if (setxattr(ctx->tmpname, acl_name, acl_data, (size_t)acl_size, 0) < 0) {
int saved_errno = errno;
free(acl_data);
zip_error_set(&ctx->error, ZIP_ER_RENAME, saved_errno);
return false;
}
free(acl_data);
}
#endif
if (chmod(ctx->tmpname, st.st_mode) < 0) {
zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
return false;
Expand Down
4 changes: 4 additions & 0 deletions regress/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ set(TEST_PROGRAMS
source_seek
)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND HAVE_GETXATTR AND HAVE_SETXATTR)
list(APPEND TEST_PROGRAMS acl_preserve)
endif()

set(GETOPT_USERS
fread
tryopen
Expand Down
6 changes: 6 additions & 0 deletions regress/acl-preserve.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description preserve POSIX access ACL when replacing archive
program acl_preserve
return 0
stdout
POSIX access ACL preserved
end-of-inline-data
169 changes: 169 additions & 0 deletions regress/programs/acl_preserve.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
acl_preserve.c -- verify that replacing an archive preserves its POSIX access ACL
SPDX-License-Identifier: BSD-3-Clause
*/

#include "config.h"

#include <endian.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/xattr.h>

#include "zip.h"

#define ACL_EA_ACCESS "system.posix_acl_access"
#define ACL_EA_VERSION 0x0002
#define ACL_UNDEFINED_ID UINT32_MAX

#define ACL_USER_OBJ 0x01
#define ACL_USER 0x02
#define ACL_GROUP_OBJ 0x04
#define ACL_MASK 0x10
#define ACL_OTHER 0x20

#define ACL_READ 0x04
#define ACL_WRITE 0x02

struct acl_header {
uint32_t version;
};

struct acl_entry {
uint16_t tag;
uint16_t permissions;
uint32_t id;
};

struct test_acl {
struct acl_header header;
struct acl_entry entries[5];
};

static void set_entry(struct acl_entry *entry, uint16_t tag, uint16_t permissions, uint32_t id) {
entry->tag = htole16(tag);
entry->permissions = htole16(permissions);
entry->id = htole32(id);
}

int main(void) {
static const char archive_name[] = "acl-preserve.zip";
static const char contents[] = "data";
unsigned char before[sizeof(struct test_acl)];
unsigned char after[sizeof(struct test_acl)];
struct test_acl acl;
zip_source_t *source = NULL;
zip_t *archive = NULL;
zip_error_t error;
ssize_t before_length;
ssize_t after_length;
int error_code;
int result = 1;

(void)remove(archive_name);

archive = zip_open(archive_name, ZIP_CREATE | ZIP_TRUNCATE, &error_code);
if (archive == NULL) {
fprintf(stderr, "cannot create archive: %d\n", error_code);
goto done;
}
source = zip_source_buffer(archive, contents, sizeof(contents) - 1, 0);
if (source == NULL || zip_file_add(archive, "data", source, 0) < 0) {
fprintf(stderr, "cannot add archive entry\n");
if (source != NULL) {
zip_source_free(source);
source = NULL;
}
zip_discard(archive);
archive = NULL;
goto done;
}
source = NULL; /* owned by archive */
if (zip_close(archive) < 0) {
fprintf(stderr, "cannot finish initial archive\n");
zip_discard(archive);
archive = NULL;
goto done;
}
archive = NULL;

acl.header.version = htole32(ACL_EA_VERSION);
set_entry(&acl.entries[0], ACL_USER_OBJ, ACL_READ | ACL_WRITE, ACL_UNDEFINED_ID);
set_entry(&acl.entries[1], ACL_USER, ACL_READ | ACL_WRITE, 65534);
set_entry(&acl.entries[2], ACL_GROUP_OBJ, 0, ACL_UNDEFINED_ID);
set_entry(&acl.entries[3], ACL_MASK, ACL_READ | ACL_WRITE, ACL_UNDEFINED_ID);
set_entry(&acl.entries[4], ACL_OTHER, 0, ACL_UNDEFINED_ID);

if (setxattr(archive_name, ACL_EA_ACCESS, &acl, sizeof(acl), 0) < 0) {
if (errno == ENOTSUP || errno == EOPNOTSUPP || errno == EPERM) {
result = 77;
goto done;
}
perror("setxattr");
goto done;
}
before_length = getxattr(archive_name, ACL_EA_ACCESS, before, sizeof(before));
if (before_length < 0) {
perror("getxattr before");
goto done;
}

archive = zip_open(archive_name, 0, &error_code);
if (archive == NULL) {
fprintf(stderr, "cannot reopen archive: %d\n", error_code);
goto done;
}
if (zip_set_archive_comment(archive, "changed", 7) < 0 || zip_close(archive) < 0) {
fprintf(stderr, "cannot update archive\n");
zip_discard(archive);
archive = NULL;
goto done;
}
archive = NULL;

after_length = getxattr(archive_name, ACL_EA_ACCESS, after, sizeof(after));
if (after_length != before_length || memcmp(before, after, (size_t)before_length) != 0) {
fprintf(stderr, "POSIX access ACL changed during archive replacement\n");
goto done;
}

zip_error_init(&error);
source = zip_source_file_create(archive_name, 0, -1, &error);
if (source == NULL) {
fprintf(stderr, "cannot create named file source: %s\n", zip_error_strerror(&error));
zip_error_fini(&error);
goto done;
}
if (zip_source_begin_write(source) < 0 || zip_source_write(source, contents, sizeof(contents) - 1) != sizeof(contents) - 1 || zip_source_commit_write(source) < 0) {
fprintf(stderr, "cannot replace named file source: %s\n", zip_error_strerror(zip_source_error(source)));
zip_source_free(source);
source = NULL;
zip_error_fini(&error);
goto done;
}
zip_source_free(source);
source = NULL;
zip_error_fini(&error);

after_length = getxattr(archive_name, ACL_EA_ACCESS, after, sizeof(after));
if (after_length != before_length || memcmp(before, after, (size_t)before_length) != 0) {
fprintf(stderr, "POSIX access ACL changed during direct source replacement\n");
goto done;
}

puts("POSIX access ACL preserved");
result = 0;

done:
if (archive != NULL) {
zip_discard(archive);
}
if (source != NULL) {
zip_source_free(source);
}
(void)remove(archive_name);
return result;
}