Date: Wed, 18 Nov 1998 16:47:45 +0100 (CET)
From: Marc Heuse <[email protected]>
To: [email protected]Subject: secure appending to files
Cc: [email protected]
Hi,
At the moment I'm thinking about the problem of appending data to files
without any evil thing which might happen.
well, at the moment there seem to be two possibilities to solve the problem,
but none of these can solve all problems. (thanks to solar designer for
showing one problem I forgot.)
(both version first try to create the file with o_excl)
version 1: lstat the target and perform all necessary checks
open file file o_rdwr and o_append
fstat on the fd
if both stat structures differ, abort
version 2: open the file o_rdwr and o_append
fstat on the fd
lstat on the target
if inode or device differ, abort
perform the necessary steps on the lstat stat structure
vulnerabilities in version 1:
a) small racecondition to replace the file after lstat and before the open
with a symlink pointing to a special file which e.g. rewinds the tape
when opened.
b) small racecondition to replace the file after lstat and before the open
with a symlink pointing to a file which is the same as the 1st file, so
the fstat structure doesn't differ. this includes the same ctime.
vulnerability in version 2:
simple DOS, just symlink the filename to a special device (see above).
a merger between version 1 + 2 would a) be a big function, b) the small
race condition for placeing the symlink to a special file.
the best solution would be an option to the open call:
fd = open ( filename, O_RDWR | O_APPEND | O_REGFILE);
to allow the open only on a regular file. with should be atomic of course.
opinions? comments? any other idea? mistakes? or did I didn't think about
someinth obvious?
below are two examples attached.
Greets,
Marc
/*
* Generic example for "How to do a secure appending on a file"
* Version 1
* Comment to [email protected]
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
/* Two topics still open:
* race condition : exchange a normal file with a symlink pointing
to a special device (e.g. rewinding tape) after the lstat
* race condition : exchange the normal file with a symlink
pointing to a regular file, where the normal file was created
in the same second + permissions etc. as the new target file.
*/
#define permissions 0644
int main () {
struct stat st;
struct stat saved_st;
FILE *f;
int fd;
char file[20] = "/tmp/xinetd.dump";
/* O_EXCL is not secure when used on an NFS mounted filesystem */
if ((fd=open(file, O_RDWR | O_CREAT | O_EXCL, permissions)) < 0) {
memset(&st, '\0', sizeof(st)); /* both stat structure filled */
memset(&saved_st, '\0', sizeof(saved_st)); /* with null-byte */
if (lstat(file, &saved_st) !=0) { /* get lstat and perform checks */
perror("lstat");
return -1;
}
if (!S_ISREG(saved_st.st_mode)) {
fprintf(stderr, "Security: %s is not a regular file, aborting.\n", file);
return -1;
}
if (saved_st.st_nlink != 1) {
fprintf(stderr, "Security: %s is hardlinked, aborting.\n", file);
return -1;
}
/*
// if it's important that the file belongs to the current euid
if (saved_st.st_uid != geteuid()) {
fprintf(stderr, "Security: I'm not owning %s, aborting.\n", file);
return -1;
}
*/
if ((fd=open(file, O_RDWR | O_APPEND)) < 0) { /* lstat data was safe */
perror("open");
return -1;
}
if (fstat(fd, &st) != 0) { /* get the stat data from the open fd */
perror("fstat");
return -1;
}
if (memcmp(&st, &saved_st, sizeof(st)) != 0) {
fprintf(stderr, "Security: %s was raced, aborting.\n", file);
close(fd); /* if both stat datas differ we've got a security */
return -1; /* problem. complain and return with an error */
}
}
/* if we arrive here, we've got a safely aquired fd */
close(fd);
return 0;
}
/*
* Generic example for "How to do a secure appending on a file"
* Version 2
* Comment to [email protected]
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
/* One topic still open:
* * easy DOS : if the target is a symlink pointing to a special device
* (e.g. rewinding tape) evil things may happen.
*/
#define permissions 0644
int main () {
struct stat st;
struct stat saved_st;
FILE *f;
int fd;
char file[20] = "/tmp/xinetd.dump";
/* O_EXCL is not secure when used on an NFS mounted filesystem */
if ((fd=open(file, O_RDWR | O_CREAT | O_EXCL, permissions)) < 0) {
if ((fd=open(file, O_RDWR | O_APPEND)) < 0) {
perror("open");
return -1;
}
memset(&st, '\0', sizeof(st)); /* both stat structure filled */
memset(&saved_st, '\0', sizeof(saved_st)); /* with null-byte */
if (fstat(fd, &st) != 0) { /* get the stat data from the open fd */
perror("fstat");
return -1;
}
if (lstat(file, &saved_st) !=0) { /* get lstat and perform checks */
perror("lstat");
return -1;
}
if ((st.st_dev != saved_st.st_dev) || ((st.st_ino != saved_st.st_ino))) {
fprintf(stderr, "Security: %s was raced, aborting.\n", file);
close(fd); /* if both stat datas differ we've got a security */
return -1; /* problem. complain and return with an error */
}
if (!S_ISREG(saved_st.st_mode)) {
fprintf(stderr, "Security: %s is not a regular file, aborting.\n", file);
return -1;
}
if (saved_st.st_nlink != 1) {
fprintf(stderr, "Security: %s is hardlinked, aborting.\n", file);
return -1;
}
/*
// if it's important that the file belongs to the current euid
if (saved_st.st_uid != geteuid()) {
fprintf(stderr, "Security: I'm not owning %s, aborting.\n", file);
return -1;
}
*/
}
/* if we arrive here, we've got a safely aquired fd */
close(fd);
return 0;
}
This message and any statements expressed therein are those of myself
and not of the Deutsche Bank AG or its subsidiary companies.
Type Bits/KeyID Date User ID
pub 2048/DB5C03C5 1997/09/23 Marc Heuse <[email protected]>
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.3i
mQENAzQnbFEAAAEIAL/tj4hn/DVjEWAZhuqRdxZQDy5B+gZbE0CD/mUnZqpem+9L
KY+I8te7jMfTQExzqn5jYb5BaibT0SbEBWSx9Gha8EiBLAVcAjvrXpV+HJLcnPRG
YDk5a3s7GrA+QVHbbd9DWgqjMfUMw9oUDAhhjgK20SeOtFGBD2U17GkQF6TK7EjC
CTOuz2Hx/tisDuroJJnxZdbLNvCceOf/D/bbFcR7DfnEJWJ3f9JC4fibZMlX5rXL
Ct/TKhZMd4d42uL7L4KvkT5JCnFuEw1jRDPpBjZ030cK2uWCM//iEVLGmGKOs6Pg
o3Lfnnd6I6bTPHgrNsapNWmocbIGDC/4w9tcA8UABRG0Jk1hcmMgSGV1c2UgPG1h
cmMuaGV1c2VAbWFpbC5kZXViYS5jb20+iQEVAwUQNCdsUQwv+MPbXAPFAQFWEwf5
AWt6PbKLLCCBPnzBMdXatKEJvNzrZRXNSpbgKQUDAKApRUnOkDJ9yp3tfJG0/BsL
XBf+ldmjjoo/OZeWhIhNb71bbCs8BK7/YK5LKef2eq4pzSiWYosrOfjlfyOVhAiP
AiWYtK/HBELy6Zs8QwoPX0QX0+R2+ocMS0TDz7nwBgO5wcj3yMU0geTrnlDpJdj1
RgFQLE6T9qO5coRjj1EAoT5gQMxP9L4TQuifYiQ6S2vh6blr3amjPohKSDzZ62/x
rQ1KMXJd7MlMQndn8UwKt4XgoFIsZOFRrkDiXfm6zFnH40UcotoA+Ygojp52+Y6A
MuixTDbuf3Jph2jEG6r4Dw==
=/n63
-----END PGP PUBLIC KEY BLOCK-----