Reading/Writing errno or stack variable causes segfault. Why? - c

I have a really weird problem here, and haven't managed to find an answer online.
It appears after debugging with printf statements that a segfault ocurred when trying to read errno. Commenting problemed lines out one by one as they cause segfault resulted in having to comment out every reference to errno, after a readdir() call reaches the end of the directory stream and returns NULL.
Even then, the code then segfaults later when trying to access another automatic variable, file_count.
What is going on? Is this a stack overflow? How do I make it stop?
The code is below, if you feel the need to wade through it. All the problematic references to errno are removed, and the program segfaults after successfully executing the third second last line: printf("printing file_count\n");.
EDIT1: Here's a GDB backtrace:
#0 0xc95bf881 in strcpy () from /usr/lib/libc.so.1
#1 0x08051543 in dir_get_list (user=0x8047b88 "user1") at maildir.c:231
#2 0x08050f3e in main (argc=4, argv=0x80479f4) at maildir.c:43
END EDIT1
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#define MAX_FILENAME_LENGTH 255
#define MAX_USERNAME_LENGTH 40
#define MAX_PASSWORD_LENGTH 20
typedef int bool;
#define true 1
#define false 0
struct files_struct{
/*The number of email messages in a maildir.*/
int count;
/*A pointer to an array of pointers to the strings of the filenames. */
char **FileNames;
/*A pointer to an array of ints that give the corresponding size of the file.*/
int *FileSize;
};
typedef struct files_struct FilesStruct;
void dir_set_path(char* path);
bool check_user(char* username, char* pass);
FilesStruct* dir_get_list(char* user);
void delete_mail(char* user, char* filename);
char* get_file(char* user, char* filename);
FilesStruct* dir_get_list(char* user){
char maildir_name[MAX_FILENAME_LENGTH];
DIR * maildir_fd;
struct dirent *maildir_p;
strcpy(maildir_name,"./");
strncat(maildir_name,user,MAX_USERNAME_LENGTH);
strcat(maildir_name,"/");
if((pthread_mutex_lock(&maildir_root_mutex))<0)
perror("ERROR on locking maildir_root_mutex");
printf("Opening directory ""%s""\n",maildir_name);
if((maildir_fd = opendir(maildir_name))==NULL)
perror("ERROR on opendir");
int file_count = 0;
/* scan over entire directory, counting number of files to that data can be properly malloced */
while(1){
if((maildir_p = readdir(maildir_fd))==NULL){
closedir(maildir_fd);
printf("breaking loop\n");
break;
}
char file[MAX_FILENAME_LENGTH+1];
strcpy(file,maildir_p->d_name);
printf("File %d: '%s'\n",file_count+1,maildir_p->d_name);
/* if the file is a file other than an email */
if(!strcmp(".",file)||!strcmp("..",file)||!strcmp("pass",file)||!strcmp(".svn",file)){
printf("Continuing without incrementing file_count\n");
continue;
}
file_count++;
}
printf("%u\n",maildir_fd);
printf("printing file_count\n");
printf("%d",file_count);
printf("file_count printed successfully");
/* Additional code OMITTED */

I came across this recently. In my instance it was that another module had declared:
int errno = 0;
as a global, instead of #including errno.h. Any code that used the "proper" errno would immediately segfault.

Related

Mapped file on Cygwin continuing to cause SIGBUS even after a call to ftruncate that fills that page

I'm trying to mmap a page-size-rounded file to an area larger than the file size and ftruncate it when one of the traling pages causes a SIGBUS so that the SIGBUS no longer happens.
This works great on Linux and MacOS, but on Cygwin I keep getting a SIGBUS even after a successful growing ftruncate.
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void perrorAndExit(char const *Ctx){ perror(Ctx); _exit(1); }
int main(){
long pgsz = sysconf(_SC_PAGESIZE);
int fd = open("TMPFILE", O_RDWR|O_CREAT,0640);
if(0>fd) perrorAndExit("open");
if(ftruncate(fd,pgsz*1)) perrorAndExit("truncate 1 pgsz");
char *m;
if(MAP_FAILED==(m = mmap(0,pgsz*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))) perrorAndExit("mmap");
memset(m,'=',pgsz);
strcpy(m,"hello, world\n");
if(ftruncate(fd,pgsz*2)) perrorAndExit("truncate 2 pgsz");
strcpy(m+pgsz,"what is up?\n"); //still SIGBUSes on Cygwin
}
Are there any workarounds for this other than starting with a larger file or creating a new mapping after the second truncate?

Running find from a C program to print file name given inode number?

I am trying to print the file name given the inode number. I am using the execlp system call to run the find command. My code is shown below:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
char str[8];
char *ptr;
ptr=str;
long x=9306140;
snprintf(str,8,"%ld", x);
execlp("find"," ","~"," ","-inum"," ",str,NULL);
}
But I get an error: Invalid argument '9306140' to -inum.
Could someone please help?
As noted in comments, you don't want the " " (space) arguments in the execlp() argument list. The shell takes a string with spaces separating the arguments, but it treats what's between the spaces as words that are passed to the command; it doesn't ordinarily pass the spaces to the command. (That's a simple and sufficient explanation for this exercise; there are caveats and weasel-words galore that could be added.)
The shell also expands ~ to match the value in the $HOME environment variable (in contrast to ~user which gets the home directory of user from the password file — they're usually, but not necessarily, the same).
You also have little margin for error in the size of string you've allocated for the number. Frankly, though, you should simply use a string. All these changes lead to:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
const char *home = getenv("HOME");
const char inode[] = "9306140";
execlp("find", "find", home, "-inum", inode, (char *)NULL);
fprintf(stderr, "failed to execute 'find' command\n");
return(EXIT_FAILURE);
}
Note the repeated "find"; the first is the name of the file to be executed after searching along the path; the second is the value to be provided as argv[0]. You could put "hypochondriac" as the second occurrence and it is likely that find would work the same, at worst reporting its error messages as coming from the program 'hypochondriac'.
The next step would be to take the inode number from a command line argument:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s inode\n", argv[0]);
exit(EXIT_FAILURE);
}
const char *home = getenv("HOME");
const char *inode = argv[1];
execlp("find", "find", home, "-inum", inode, (char *)NULL);
fprintf(stderr, "%s: failed to execute 'find' command\n", argv[0]);
return(EXIT_FAILURE);
}
The step after that would be to handle multiple inode values; at that point, you use execvp() instead of execlp(), though (or, if you're desparate and lazy, you loop and fork() and execlp() once per inode number, but that's slamming your system for no good reason at all).

Setting Immutable Flag using ioctl() in C

I have attempted to make a script that creates a file and then sets it as immutable similar to the chattr +i command for linux. The script compiles (with gcc), runs and the file is created. However the file itself is not immutable and can be removed with a simple rm -f. I have attempted to stacktrace where chattr is called and I found a function called ioctl. I then used what little information I could gather and came up with what I have below. I narrowed it down from ext2_fs.h but it just doesn't seem to work. I've clearly overlooked something.
Updates to previous entry: Compiles but returns -1 on ioctl() function. Bad address shown with perror().
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
int main()
{
FILE *fp;
char shovel[16] = "I have a shovel!";
fp = fopen("/shovel.txt", "w+");
fwrite(shovel, sizeof(shovel[0]), sizeof(shovel)/sizeof(shovel[0]), fp);
ioctl(fileno(fp), FS_IOC_SETFLAGS, 0x00000010);
fclose(fp);
}
Any help appreciated.
You are using the right ioctl command, but you're passing it the wrong arguments.
The manpage for ioctl_list(2) shows that FS_IOC_SETFLAGS expects to receive a pointer to int (an int *), yet you're passing it an integer literal (hence the Bad Address error).
The fact that you don't to any error checking whatsoever is also not helping.
The correct flag to pass to FS_IOC_SETFLAGS is a pointer holding the value EXT2_IMMUTABLE_FL, which is defined in ext2fs/ext2_fs.h (some older / different Linux distributions seem to have it under linux/ext2_fs.h), so you'll need to #include <ext2fs/etx2_fs.h>. Make sure to install e2fslibs-dev (and probably you'll need linux-headers too).
This code is working:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <ext2fs/ext2_fs.h>
int main()
{
FILE *fp;
char shovel[16] = "I have a shovel!";
if ((fp = fopen("shovel.txt", "w+")) == NULL) {
perror("fopen(3) error");
exit(EXIT_FAILURE);
}
fwrite(shovel, sizeof(shovel[0]), sizeof(shovel)/sizeof(shovel[0]), fp);
int val = EXT2_IMMUTABLE_FL;
if (ioctl(fileno(fp), FS_IOC_SETFLAGS, &val) < 0)
perror("ioctl(2) error");
fclose(fp);
return 0;
}
Remember to run this as root.
UPDATE:
As Giuseppe Guerrini suggests in his answer, you might want to use FS_IMMUTABLE_FL instead, and you won't need to include ext2_fs.h:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
int main()
{
FILE *fp;
char shovel[16] = "I have a shovel!";
if ((fp = fopen("shovel.txt", "w+")) == NULL) {
perror("fopen(3) error");
exit(EXIT_FAILURE);
}
fwrite(shovel, sizeof(shovel[0]), sizeof(shovel)/sizeof(shovel[0]), fp);
int val = FS_IMMUTABLE_FL;
if (ioctl(fileno(fp), FS_IOC_SETFLAGS, &val) < 0)
perror("ioctl(2) error");
fclose(fp);
return 0;
}
The main problem is that the ioctl wants a pointer to the mask, not a direct constant. You have to define a int variable, store the mask (0x10) in it and pass its address as third argument of ioctl.
Also, I'd add some hints:
other programs to change attributes are used to use low-level I/O directly (open, close...). Also, the file is usually opened with O_RDONLY.
Use FS_IMMUTABLE_FL istead the raw constant.
Get the current attribute mask first (FS_IOC_SETFLAGS) and mask it with the new flag, so other settings are not lost by the service.

Segmentation fault in C when passing argument to a function

I am new to this forum, so please bear with me.
I am currently writing a program that will back up specific files and each time it is backed up a date and time stamp are added to the file name. At this stage I am writing a function that will identify all the files in the present working directory and will then read it into an array and will later on back up each of those files. The backing up works fine.
The problem I am having is that each time I attempt to pass a folder name to my function (as a string) I get a segmentation fault the debugger gives this error:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400b48 in backupAllFiles (
dirname=) at processing.c:52
52 {
The backupAllFiles() function is written inside the processing.c source file (it is a multi-source file project). Here is a snippet of the code that is the main concern:
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <getopt.h>
void backupAllFiles(char *dirname)
{
int numfiles = 0;
char allFiles[MAXPATHLEN][MAXPATHLEN];
DIR *dirp;
struct dirent *dp;
dirp = opendir(dirname);
if(dirp == NULL)
{
fprintf(stderr, "Could not open: %s.\n",dirname);
exit(EXIT_FAILURE);
}
while((dp = readdir(dirp)) != NULL)
{
struct stat stat_buffer;
if(stat(allFiles[numfiles], &stat_buffer) != 0)
{
fprintf(stderr,"An error occurred while reading the directory: %s\n",dirname);
}
else if(S_ISREG(stat_buffer.st_mode))
{
sprintf(allFiles[numfiles], "%s%s", dirname, dp->d_name);
}
numfiles++;
}
for(int i = 0; i < numfiles; i++)
{
fprintf(stderr,"All the files are (%i) %s.\n",i ,allFiles[i]);
}
}
int main(int argc, char *argv[])
{
fprintf(stderr, "Entered main, passing argument %s to backupAllFiles()\n",argv[1]);
backupAllFiles(argv[1]);
return 0;
}
Please note this is not part of the main program yet, this is merely a separated segment which is used for individual testing.
I am really stumped as to why the segfault occurs and I have tried strdup for argv[1], however to no avail.
I appreciate all and any help. :)
P.S. There will be extra inclusions that are not necessary, this is for the rest of the program, I was too lazy to find which would exclusively apply to this function.
Just to recap some of the discussion into an answer: there are at least two problems with this code.
Declaring char allFiles[MAXPATHLEN][MAXPATHLEN] on the stack tries to allocate more memory than the system is willing to permit, which is why the segmentation fault is raised as soon as the code enters backupAllFiles.
The stat call in backupAllFiles accesses the allFiles[numfiles] element before initializing it. This should probably be
if(stat(dp->d_name, &stat_buffer) != 0)
rather than
if(stat(allFiles[numfiles], &stat_buffer) != 0)
Thanks to #wildplasser and #Carl Norum for identifying that the underlying problem here was, literally, stack overflow.

Login simulator in c, dereferencing incomplete types errors

As a preporatory task for a computer lab in school, we are asked to write a c program that simulates the login process in UNIX. The program should read username and password from a terminal, compare it to hashed values in a local file that is supposed to resemble /etc/passwd.
Here's what I've got:
/*
* Program mylogin.c
*
* This program prompts the user for a login name and password
*
*/
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <string.h>
/* define some error constants */
#define NOUSER -1
/* define max size of a username */
#define USERNAME_SIZE 32
#define PASSWORD_SIZE 32
#define HASH_SIZE 32
#define FAILED_LIMIT 5
#define AGE_LIMIT 10
int read_username(char *username)
{
printf("login: ");
fgets(username,USERNAME_SIZE,stdin);
/* remove the CR included by getline() */
username[strlen(username)-1]='\0';
return(0);
}
int read_password(char *password)
{
printf("password: ");
fgets(password,PASSWORD_SIZE,stdin);
//getpass(password);
/* remove the CR included by getline() */
password[strlen(password)-1]='\0';
return(0);
}
int user_exists(const char *username)
{
struct pwdb_passwd *pw_entry;
pw_entry=getpwnam(username);
return((pw_entry)!=NULL);
}
int main(int argc,char **argv)
{
char username[USERNAME_SIZE];
char* password;
/* write "login:" and read user input */
read_username(username);
read_password(password);
if (!user_exists(username))
{
printf("Unknown user or authentication\n");
main(argc, argv);
}
struct pwdb_passwd *pw_entry = getpwnam(username);
char* hashed_password = crypt(password,pw_entry->pw_passwd);
if(strcmp(hashed_password, pw_entry->pw_passwd)==0)
{
if((pw_entry->pw_failed)<FAILED_LIMIT)
{
printf("User authenticated successfully\n");
pw_entry->pw_age++;
pw_entry->pw_failed = 0;
pwdb_update_user(pw_entry);
}else{
printf("User account locked\n");
main(argc, argv);
}
}
else
{
printf("Unknown user or authentication\n");
pw_entry->pw_failed++;
if(pw_entry->pw_failed>5){
printf("Too many failed attempts. Username now locked\n");
}
pwdb_update_user(pw_entry);
main(argc, argv);
}
return(0);
}
The struct pwdb_passwd is defined in the files pwdb_lib.c and pwdb_lib.h, which are already written.
When I compile the program, I get a couple of errors. For example on line 73, I get: "error: dereferencing pointer to incomplete type"
I don't understand why. It doesn't seem to like pw_entry->pw_passwd and things like that. More to the point, I get different errors when compiling under Windows with Code::Blocks (using gcc) than under Ubuntu with gcc. I find this pretty strange. I suspect it could be because I import pwd.h and that it only exists on Linux and not Windows. Could this be right? I tried creating my own pwd.h file and save it in the same directory, but it still didn't work. Moving to an ubuntu computer, I dont get errors from the pwd.h thing, but instead get errors on: "dereferencing pointer to incomplete type"
What's wrong with my code?
I also suspect memory leak in the user_exists function, but I'm not sure if it affects the overall program.
Even though pwdb_lib.c is already written, you need to include it in your source file.
Add
#include "pwdb_lib.h"
to your source and make sure you compile/link against pwdb_lib.c
By #includeing this file, you let your source file know about the definitions within it without providing it the implementation. At the end, when you compile your program with pwdb_lib.c (or link its object file, if that's what you're doing), you let any source which included these definitions know where they are implemented (and thus, give them the ability to use them).
If the header is named pwdb_lib.h, then why isn't your program doing an #include of it? It seems to include a different header (pwd.h), which is it?
The error you're getting is the one you'd expect if the declarations were missing.

Resources