/* cryogenic.c copyright 2000 Dominique Brezinski * * All rights granted for commercial and non-commercial use * * Captures process information stored in Linux's Proc_fs on a best effort * basis * * Intended for system integrity analysis * * compile: gcc -o cryogenic -static cryogenic.c * * commandline: cryogenic [-p PID] * */ #include #include #include #include #include #include #include #include /* definition of constant values */ #define STRING_MAX 1024 #define PIDSTR_MAX 9 #define BUFSIZE 16384 #define LINKS_MAX 512 #define DIRS_AND_DESC "dirs_and_descriptors" /* function prototypes */ void Usage(void); int FreezeProcInfo(char*, char*); int FindAllAndFreeze(char*); int CreateLinksArray(char[LINKS_MAX + 1][STRING_MAX + 1], char*); /* external variables used by getopt() */ extern int optind; extern char* optarg; /* main() * * Parse commandline, check validity of arguments, and call functions * that do real work. * */ int main(int argc, char** argv) { char output_root[STRING_MAX + 1] = {'\0'}; int pid = -1; int arg = -1; char pidstr[PIDSTR_MAX + 1] = {'\0'}; char* endptr = NULL; DIR* dir = NULL; /* arguments are required, so give usage summary if no * args are given */ if (argc < 2) { Usage(); exit(1); } /* loop while getopt() continues to find arguments */ while (-1 != (arg = getopt(argc, argv, "p:"))) { switch (arg) { /* -p is the only optional argument at this * point. We attempt to convert the argument * string to an integer and check if it is in * the valid range for a process ID. */ case 'p': pid = strtol(optarg, &endptr, 10); if (*optarg != '\0' && *endptr == '\0') { if (pid > PID_MAX || pid < 1) { fprintf(stderr, "\nPID out of valid range\n"); exit(1); } else { /* looks valid, so copy the string * to an internal buffer. */ strncpy(pidstr, optarg, PIDSTR_MAX); } } break; case ':': case '?': default: Usage(); exit(1); break; } /* end switch() */ } /* end while() */ /* an output directory is required, so get it or bail out */ if (NULL != argv[optind]) { strncpy(output_root, argv[optind], STRING_MAX); } else { fprintf(stderr, "\nOUTPUT_DIRECTORY is required\n\n"); Usage(); exit(1); } /* check if output directory exists */ if (NULL == (dir = opendir(output_root))) { fprintf(stderr, "\nOUTPUT_DIRECTORY \"%s\" is not a valid directory\n", output_root); exit(1); } else { closedir(dir); /* do a dumb little test that is not really even valid */ if (strlen(output_root) > STRING_MAX - (2 * PIDSTR_MAX) - 1) { fprintf(stderr, "\nOUTPUT_DIRECTORY name too long\n"); exit(1); } else { /* the output directory looks valid, so append * a slash for easy concatenation later */ strcat(output_root, "/"); } } /* if -p was specified, then just process the specific PID */ if (-1 != pid) { if (!FreezeProcInfo(pidstr, output_root)) { fprintf(stderr, "Failed to freeze process info\n"); exit(1); } } else { /* we are going to capture all process information */ if (!FindAllAndFreeze(output_root)) { fprintf(stderr, "Failed to freeze all process info\n"); exit(1); } } exit(0); } void Usage(void) { printf("\tcryogenic [-p PID] OUTPUT_DIRECTORY\n"); printf("\n\tOUTPUT_DIRECTORY\tThe directory to hold output\n"); printf("\t\t\t\tsubdirectories will be created for each process\n"); printf("\n\t-p PID\t\t\tCreate output for a specific process\n"); printf("\t\t\t\tidentified by its PID\n"); } /* FreezeProcInfo() * * arguments: string representation of a process ID (PID) * name of output directory * * action: create subdirectory (PID value) in output directory, grok * proc_fs data for PID and place it in files in subdir. * * return: 1 on success, 0 on failure */ int FreezeProcInfo(char* pidstr, char* output_root) { char infilebase[STRING_MAX + 1] = {'\0'}; char outfilebase[STRING_MAX + 1] = {'\0'}; char infile[STRING_MAX + 1] = {'\0'}; char outfile[STRING_MAX + 1] = {'\0'}; int fdinput = -1; int fdoutput = -1; FILE* outstream = NULL; int count = 0; char buf[BUFSIZE] = {'\0'}; int bytes = 0; /* files in /proc/PID that we should copy--last string must be * '\0' (NULL) */ char procfiles[6][STRING_MAX+1] ={{'c','m','d','l','i','n','e','\0'}, {'e','n','v','i','r','o','n','\0'}, {'e','x','e','\0'}, {'m','a','p','s','\0'}, {'s','t','a','t','u','s','\0'}, {'\0'}}; /* this array gets dynamically built for each process. It ends up * containing /proc/PID/cwd, /proc/PID/root, and the entries from * /proc/PID/fd. Since these are all links, we actually are * interested in what they point to, not the contents of what * they point to. */ char proclinks[LINKS_MAX + 1][STRING_MAX + 1] = {{'\0'}}; /* create proc directory base name for process */ strncpy(infilebase, "/proc/", STRING_MAX); strncat(infilebase, pidstr, STRING_MAX - strlen(infilebase)); strncat(infilebase, "/", STRING_MAX - strlen(infilebase)); /* create output directory base name for process and create * subdirectory */ strncpy(outfilebase, output_root, STRING_MAX); strncat(outfilebase, pidstr, STRING_MAX - strlen(outfilebase)); strncat(outfilebase, "/", STRING_MAX - strlen(outfilebase)); if (mkdir(outfilebase, S_IRWXU)) { fprintf(stderr, "\nCould not create %s\n", outfilebase); return 0; } /* loop through all files in the procfiles array for this * process */ for (count = 0; *(procfiles[count]) != '\0'; count++) { /* generate the file name for the current process we * are working on, and then try to open it */ strncpy(infile, infilebase, STRING_MAX); strncat(infile, procfiles[count], STRING_MAX - strlen(infile)); if (-1 == (fdinput = open(infile, O_RDONLY))) { fprintf(stderr, "\nCould not open %s\n", infile); continue; } /* generate the output file name and try to open it */ strncpy(outfile, outfilebase, STRING_MAX); strncat(outfile, procfiles[count], STRING_MAX -strlen(outfile)); /* we are trying to do exclusive file creation here, just * to make sure we don't write over any existing data */ if (-1 == (fdoutput = open(outfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR))) { fprintf(stderr, "\nCould not open %s\n", outfile); close(fdinput); continue; } /* copy the target file for the process to its corresponding * output file */ while (0 < (bytes = read(fdinput, buf, BUFSIZE))) { if (0 >= (bytes = write(fdoutput, buf, bytes))) { fprintf(stderr, "Failed to write %d bytes to %s\n", bytes, outfile); bytes = -1; break; } } if (bytes < 0) { fprintf(stderr, "Failed to copy %s to %s\n", infile, outfile); } close(fdinput); close(fdoutput); } /* end of for() */ /* call out to build array of links in process proc entry */ if (!CreateLinksArray(proclinks, infilebase)) { fprintf(stderr, "\nFailed to create array of links for %s\n", infilebase); return 0; } /* create output file name for link information */ strncpy(outfile, outfilebase, STRING_MAX); strncat(outfile, DIRS_AND_DESC, STRING_MAX - strlen(outfile)); if (-1 == (fdoutput = open(outfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR))) { fprintf(stderr, "\nCould not open %s\n", outfile); return 0; } /* convert file descriptor for link information output file to * a stream, so we can easily fprint() info to file */ if (NULL == (outstream = fdopen(fdoutput, "w"))) { fprintf(stderr, "\nCould not open %s\n", outfile); close(fdoutput); return 0; } /* loop through each link name in proclinks array */ for (count = 0; *(proclinks[count]) != '\0'; count++) { char linkinfo[STRING_MAX + 1] = {'\0'}; /* get the name of what the link points to, because that * is what we are interested in--not the file itself */ if (-1 == readlink(proclinks[count], linkinfo, STRING_MAX)) { fprintf(stderr, "\nFailed to read link info for %s\n", proclinks[count]); } /* write link info to output file */ fprintf(outstream, "%s -> %s\n", proclinks[count], linkinfo); memset(linkinfo, '\0', STRING_MAX); } fclose(outstream); close(fdoutput); return 1; } /* CreateLinksArray() * * fill the proclinks array with the filenames of the interesting links * in the target process' proc entry. We hard code the 'cwd' and 'root' * links, and we dynamically add the links in the 'fd' directory of the * proc entry. * * return: 1 on success, 0 on failure * */ int CreateLinksArray(char proclinks[LINKS_MAX + 1][STRING_MAX + 1], char* infilebase) { DIR* dir = NULL; struct dirent* dirent = NULL; char dirname[STRING_MAX + 1] = {'\0'}; int i = 0; /* create the file descriptor directory name for the target * process--should look something like /proc/PID/fd/ */ strncpy(dirname, infilebase, STRING_MAX); strncat(dirname, "fd/", STRING_MAX - strlen(dirname)); /* hardcode in the current working directory link name */ strncpy(proclinks[i], infilebase, STRING_MAX); strncat(proclinks[i++], "cwd", STRING_MAX - strlen(proclinks[0])); /* hardcode in the process root link name */ strncpy(proclinks[i], infilebase, STRING_MAX); strncat(proclinks[i++], "root", STRING_MAX - strlen(proclinks[1])); /* open the file descriptor directory */ if (NULL == (dir = opendir(dirname))) { fprintf(stderr, "\nFailed to open %s\n", dirname); return 0; } /* loop through the directory entries adding them to the array */ for (dirent = readdir(dir); dirent != NULL && i < LINKS_MAX; dirent = readdir(dir)) { /* exclude the standard current and parent directory * alias entries */ if (!strncmp(dirent->d_name, ".", 2) || !strncmp(dirent->d_name, "..", 3)) { continue; } /* generate the name for the descriptor link */ strncpy(proclinks[i], dirname, STRING_MAX); strncat(proclinks[i++], dirent->d_name, STRING_MAX - strlen(proclinks[i])); } closedir(dir); return 1; } /* FindAllAndFreeze() * * Opens up /proc, reads the entries in the directory, and for the * numeric ones it calls FreezeProcInfo(). * * return: 1 on success, 0 on failure * */ int FindAllAndFreeze(char* output_root) { DIR* dir = NULL; struct dirent* dirent = NULL; char* endptr = NULL; /* open the /proc directory */ if (NULL == (dir = opendir("/proc"))) { fprintf(stderr, "\nFailed to open /proc\n"); return 0; } /* loop through the directory entries */ for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) { /* skip the current and parent dir aliases */ if (!strncmp(dirent->d_name, ".", 2) || !strncmp(dirent->d_name, "..", 3)) { continue; } /* check if the entry is numeric. if it is, call * FreezeProcInfo() using the value. */ strtol(dirent->d_name, &endptr, 10); if (*(dirent->d_name) != '\0' && *endptr == '\0') { if (!FreezeProcInfo(dirent->d_name, output_root)) { fprintf(stderr, "\nCould not freeze process %s\n", dirent->d_name); } } endptr = NULL; } closedir(dir); return 1; }