From 436b66ba974592b7cd7aa7e4b1744894ee79f785 Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Tue, 26 Aug 2008 15:08:46 +0400 Subject: [PATCH 3/4] New utility: pkg_audit It is mainly a helper for portupgrade to avoid awk scripting and numerous calls for pkg_info. This utility speeds up portaudit by a factor of 10 on a system with 521 installed ports and the auditfile that contains 3213 entries: ----- $ ls -d /var/db/pkg/* | wc -l 521 $ tar xOf /var/db/portaudit/auditfile.tbz auditfile | sed -e'/^#/d' | wc -l 3213 $ time ./portaudit Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- DNS spoofing vulnerability. Reference: Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- DoS vulnerability in WEBrick. Reference: Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- multiple vulnerabilities in safe level. Reference: 3 problem(s) in your installed packages found. You are advised to update or deinstall the affected package(s) immediately. real 0m0.107s user 0m0.116s sys 0m0.012s $ time portaudit Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- multiple vulnerabilities in safe level. Reference: Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- DoS vulnerability in WEBrick. Reference: Affected package: ruby-1.8.6.111_4,1 Type of problem: ruby -- DNS spoofing vulnerability. Reference: 3 problem(s) in your installed packages found. You are advised to update or deinstall the affected package(s) immediately. real 0m1.583s user 0m0.560s sys 0m1.057s ----- Signed-off-by: Eygene Ryabinkin --- usr.sbin/pkg_install/Makefile | 2 +- usr.sbin/pkg_install/audit/Makefile | 14 ++ usr.sbin/pkg_install/audit/audit.h | 43 ++++++ usr.sbin/pkg_install/audit/main.c | 167 ++++++++++++++++++++ usr.sbin/pkg_install/audit/parse.c | 259 ++++++++++++++++++++++++++++++++ usr.sbin/pkg_install/audit/pkg_audit.1 | 63 ++++++++ 6 files changed, 547 insertions(+), 1 deletions(-) create mode 100644 usr.sbin/pkg_install/audit/Makefile create mode 100644 usr.sbin/pkg_install/audit/audit.h create mode 100644 usr.sbin/pkg_install/audit/main.c create mode 100644 usr.sbin/pkg_install/audit/parse.c create mode 100644 usr.sbin/pkg_install/audit/pkg_audit.1 diff --git a/usr.sbin/pkg_install/Makefile b/usr.sbin/pkg_install/Makefile index bf1a213..0d7536d 100644 --- a/usr.sbin/pkg_install/Makefile +++ b/usr.sbin/pkg_install/Makefile @@ -2,7 +2,7 @@ .include -SUBDIR= lib add create delete info updating version +SUBDIR= lib add create delete info updating version audit .include diff --git a/usr.sbin/pkg_install/audit/Makefile b/usr.sbin/pkg_install/audit/Makefile new file mode 100644 index 0000000..2ece5f8 --- /dev/null +++ b/usr.sbin/pkg_install/audit/Makefile @@ -0,0 +1,14 @@ +# $FreeBSD$ + +PROG= pkg_audit +SRCS= main.c parse.c + +CFLAGS+= -I${.CURDIR}/../lib + +WARNS?= 6 +WFORMAT?= 1 + +DPADD= ${LIBINSTALL} ${LIBFETCH} ${LIBMD} +LDADD= ${LIBINSTALL} -lfetch -lmd + +.include diff --git a/usr.sbin/pkg_install/audit/audit.h b/usr.sbin/pkg_install/audit/audit.h new file mode 100644 index 0000000..1f0a369 --- /dev/null +++ b/usr.sbin/pkg_install/audit/audit.h @@ -0,0 +1,43 @@ +/* + * + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * 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. + * + * Eygene Ryabinkin + * 26 August 2008 + * + * Parsing module for pkg_audit, header file. + * + */ + +#ifndef __AUDIT_H__ +#define __AUDIT_H__ + +#include + +SLIST_HEAD(audit_contents, audit_entry); + +struct audit_entry { + char *pkgglob; /* Package name glob */ + char *url; /* URL of advisory */ + char *descr; /* Description of vulnerability */ + size_t pfx_size; /* Metacharacter-less glob part size */ + SLIST_ENTRY(audit_entry) entries; +}; + + +/* Function prototypes */ +int +parse_auditfile(FILE *_fp, struct audit_contents *_head); + + +#endif /* defined(__AUDIT_H__) */ diff --git a/usr.sbin/pkg_install/audit/main.c b/usr.sbin/pkg_install/audit/main.c new file mode 100644 index 0000000..7e33690 --- /dev/null +++ b/usr.sbin/pkg_install/audit/main.c @@ -0,0 +1,167 @@ +/* + * + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * 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. + * + * Eygene Ryabinkin + * 26 August 2008 + * + * This is the audit module -- fast helper for the portaudit script. + * + * It is filter-like utility: it reads the audit file from the + * standard input, intersects the vulnerable port list with the + * packages installed in the system and outputs the entries for + * the vulnerable ports that are present in the system to the + * standard output. + * + * The installed package can be listed multiple times, since it + * can be vulnerable to more than one bug at a time. But the + * whole output entries will be unique -- package name and + * vulnerability details should produce unique entry. + * + * One more field is prepended to the list of the input fields -- + * the name of the matched port. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#ifdef PROFILING +#include +#endif + +#include +#include +#include +#include +#include + +#include "lib.h" +#include "audit.h" + +static inline void +audit_package(const char *_pkgname, struct audit_contents *_head, + struct match_session *_msess, FILE *_fp); + +int +main(int argc, char *argv[]) +{ + char freebsd[sizeof("FreeBSD-XXYYZZXXYYZZ")]; + unsigned long reldate; + size_t reldate_size = sizeof(reldate); + int mib[2]; + + FILE *in = stdin, *out = stdout; + struct match_session *msess; + struct audit_entry *item; +#ifdef PROFILING + struct timeval t1, t2; + double dt; +#endif + + struct audit_contents head = + SLIST_HEAD_INITIALIZER(head); + + /* Make compiler happy */ + if (argv[argc] == NULL) {}; + + mib[0] = CTL_KERN; + mib[1] = KERN_OSRELDATE; + if (sysctl(mib, 2, (void *)&reldate, &reldate_size, NULL, 0) != 0) + errx(1, "Unable to get kern.osreldate"); + snprintf(freebsd, sizeof(freebsd), "%s-%lu", "FreeBSD", reldate); + + SLIST_INIT(&head); +#ifdef PROFILING + gettimeofday(&t1, NULL); +#endif + if (parse_auditfile(in, &head) != 0) { + errx(1, "Failed to parse audit entries"); + } +#ifdef PROFILING + gettimeofday(&t2, NULL); + dt = t2.tv_sec - t1.tv_sec + 1e-6 * (t2.tv_usec - t1.tv_usec); + fprintf(stderr, "parse_auditfile(): %.6lf sec\n", dt); +#endif + + msess = match_begin(MATCH_GLOB); + if (msess == NULL) + return 1; + +#ifdef PROFILING + gettimeofday(&t1, NULL); +#endif + + /* Special check: FreeBSD itself */ + SLIST_FOREACH (item, &head, entries) { + if (strncmp(item->pkgglob, + "FreeBSD", sizeof("FreeBSD") - 1) == 0 && + pattern_match(MATCH_GLOB, item->pkgglob, freebsd)) { + fprintf(out, "%s|%s|%s|%s\n", + freebsd, + item->pkgglob, item->url, item->descr); + } + } + + /* Installed packages */ + while (match_next_package(msess)) + audit_package(match_get_pkgname(msess), &head, msess, out); + +#ifdef PROFILING + gettimeofday(&t2, NULL); + dt = t2.tv_sec - t1.tv_sec + 1e-6 * (t2.tv_usec - t1.tv_usec); + fprintf(stderr, "match loop: %.6lf sec\n", dt); +#endif + + match_end(msess); + + SLIST_FOREACH (item, &head, entries) { + free((void *)item->pkgglob); + free((void *)item); + } + + return 0; +} + +void +cleanup(int sig) +{ + sig = 0; + return; +} + +/* + * Loops over audit file contents and checks each entry in turn. + * + * The great speedup is to test the package prefix at the first + * place and only if it matches perform full match -- match_matches + * uses slow matching routines without precompilation and other + * tricks. For hundreds of installed ports and a couple of thousands + * audit entries this slows things down very well. + */ +static inline void +audit_package(const char *pkgname, struct audit_contents *head, + struct match_session *msess, FILE *fp) +{ + struct audit_entry *item; + + SLIST_FOREACH (item, head, entries) { + if (strncmp(pkgname, item->pkgglob, + item->pfx_size) == 0 && + match_matches(msess, item->pkgglob)) { + fprintf(fp, "%s|%s|%s|%s\n", + pkgname, + item->pkgglob, item->url, item->descr); + } + } +} diff --git a/usr.sbin/pkg_install/audit/parse.c b/usr.sbin/pkg_install/audit/parse.c new file mode 100644 index 0000000..ab5f645 --- /dev/null +++ b/usr.sbin/pkg_install/audit/parse.c @@ -0,0 +1,259 @@ +/* + * + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * 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. + * + * Eygene Ryabinkin + * 26 August 2008 + * + * Parsing module for pkg_audit. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include "lib.h" +#include "audit.h" + +/* Simple exponentially-growing buffer. */ + +struct dyn_buffer { + char *buf; + size_t size; +}; + +/* Prototypes */ +static int +parse_audit_entry(struct dyn_buffer *_b, struct audit_entry *_e); + +static int +read_line(FILE *_fp, struct dyn_buffer *_b); + +static struct dyn_buffer * +buf_init(size_t _size); +static void +buf_destroy(struct dyn_buffer *_b); +static int +buf_grow(struct dyn_buffer *_b); + +/* + * Parses audit file to the linked list of single entries. + * + * Return values: + * 0 -- file was successfully parsed; + * 1 -- parsing or read error occured; + * -1 -- bad arguments, memory allocation problems, etc. + */ +int +parse_auditfile(FILE *fp, struct audit_contents *head) +{ + struct audit_entry *e; + struct dyn_buffer *b; + int errcode; + + b = buf_init(256); + if (b == NULL) + return 1; + + while ((errcode = read_line(fp, b)) == 0) { + if (b->buf[0] == '#') + continue; + e = (struct audit_entry *)malloc(sizeof(*e)); + if (e == NULL) { + buf_destroy(b); + return -1; + } + bzero((void *)e, sizeof(*e)); + if ((errcode = parse_audit_entry(b, e)) != 0) { + buf_destroy(b); + return errcode; + } + SLIST_INSERT_HEAD(head, e, entries); + } + + buf_destroy(b); + + if (errcode != 1) + return 1; + else + return 0; +} + +/* + * Helpers for audit file parsing routine. + */ + +static struct dyn_buffer * +buf_init(size_t size) +{ + struct dyn_buffer *b; + + if (size <= 0) + return NULL; + + b = (struct dyn_buffer *)malloc(sizeof(*b)); + if (b == NULL) + return NULL; + + bzero((void *)b, sizeof(*b)); + b->size = size; + b->buf = (char *)malloc(b->size * sizeof(b->buf[0])); + if (b->buf == NULL) { + free((void *)b); + return NULL; + } + + bzero((void *)b->buf, b->size); + return b; +} + +static void +buf_destroy(struct dyn_buffer *b) +{ + if (b == NULL) + return; + + if (b->buf != NULL) + free((void *)b->buf); + free((void *)b); + return; +} + +static int +buf_grow(struct dyn_buffer *b) +{ + char *newbuf; + + if (b == NULL || b->buf == NULL || b->size <= 0) + return -1; + + newbuf = (char *)malloc(2 * b->size * sizeof(newbuf)); + if (newbuf == NULL) + return 1; + + bzero(newbuf, 2 * b->size); + bcopy((void *)b->buf, (void *)newbuf, b->size); + b->buf = newbuf; + b->size *= 2; + + return 0; +} + +/* + * fgets()-like function that reads the whole input line + * Returns 0 on the successful read, 1 for the end-of-file + * condition, -1 for any error. + * + * Terminating '\n' is removed from the line. + */ +static int +read_line(FILE *fp, struct dyn_buffer *b) +{ + size_t offset = 0, len = 0; + + if (fp == NULL || b == NULL) + return -1; + + if (feof(fp)) + return 1; + + /* We need at least two-character buffer */ + if (b->size == 1 && buf_grow(b) != 0) + return -1; + + b->buf[b->size - 1] = '\0'; + offset = 0; + while (fgets(b->buf + offset, b->size - offset, fp) != NULL) { + len = strlen(b->buf); + /* + * Read zero characters or buffer even shrinked? + * Strange, let's indicate error. + */ + if (len <= offset) + return -1; + if (b->buf[len - 1] == '\n') { + b->buf[len - 1] = '\0'; + return 0; + } + + offset = len; + if (buf_grow(b) != 0) + return -1; + + /* Should not happen, but who knows */ + if (offset >= b->size) + return -1; + } + + if (feof(fp)) { + /* + * If we read no characters, if means that we were + * at the EOF, but it was detected only by fgets(), + * not the first feof(). + */ + if (len == 0) + return 1; + else + return 0; + } else { + return -1; + } +} + +/* + * Parses single audit line and places it to the structure. + * Calculates length of the package name suffix that is free + * from metacharacters -- it is used for the quick matches + * against port name. + */ +static int +parse_audit_entry(struct dyn_buffer *b, struct audit_entry *e) +{ + size_t len; + char *string = NULL, *d1 = NULL, *d2 = NULL; + static const char globset[] = "{*?><=!"; + + /* + * At least 5 characters: + * two delimiters and three non-empty fields. + */ + len = strlen(b->buf); + if (len < 5) + return 1; + + /* Locate delimiters. */ + d1 = strchr(b->buf, '|'); + if (d1 == NULL) + return 1; + d2 = strchr(d1 + 1, '|'); + if (d2 == NULL) + return 1; + + string = (char *)malloc((len + 1) * sizeof(string[0])); + if (string == NULL) + return -1; + + bcopy((void *)b->buf, (void *)string, (len + 1) * sizeof(string[0])); + string[d1 - b->buf] = '\0'; + string[d2 - b->buf] = '\0'; + e->pkgglob = string; + e->url = string + (d1 - b->buf) + 1; + e->descr = string + (d2 - b->buf) + 1; + e->pfx_size = strcspn(e->pkgglob, globset); + + return 0; +} diff --git a/usr.sbin/pkg_install/audit/pkg_audit.1 b/usr.sbin/pkg_install/audit/pkg_audit.1 new file mode 100644 index 0000000..cd4abbc --- /dev/null +++ b/usr.sbin/pkg_install/audit/pkg_audit.1 @@ -0,0 +1,63 @@ +.\" +.\" FreeBSD install - a package for the installation and maintenance +.\" of non-core utilities. +.\" +.\" 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. +.\" +.\" Eygene Ryabinkin +.\" +.\" +.\" @(#)pkg_audit.1 +.\" $FreeBSD$ +.\" +.Dd Aug 26, 2008 +.Dt PKG_AUDIT 1 +.Os +.Sh NAME +.Nm pkg_audit +.Nd lists vulnerable ports installed in the system +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +command is used to extract vulnerability information from the audit +file and list vulnerable packages that are present in the system. +It is main purpose to help +.Xr portaudit 1 +utility to avoid time-consuming scripting. +.Nm +reads vulnerability information from the standard input and writes +the list of vulnerable ports to the standard output. +Format of the output lines is the same as for the audit file, but +package matching globs are substituted with the actual package names. +.Sh TECHNICAL DETAILS +First the audit file is parsed to the internal representation +(currently it is linked list). +Then we are traversing installed packages database and trying to +match the package name against each audit entry. +The crucial step for the speeding up the process is to first +match the package prefix that has no CSH-like metacharacters +and perform full comparison only if match is found. +One more name is tested prior to the installed packages: it is +.Qo FreeBSD-`sysctl -n kern.osreldate` Qc , +the version of +.Fx +the current system is running. +.Sh SEE ALSO +.Xr portaudit 1 , +.Xr pkg_add 1 , +.Xr pkg_create 1 , +.Xr pkg_delete 1 , +.Xr pkg_version 1 . +.Sh AUTHORS +.An Eygene Ryabinkin Aq rea-fbsd@codelabs.ru +.Sh BUGS +Sure to be some. -- 1.6.2.4