MFHead@r345880

This commit is contained in:
Alan Somers 2019-04-04 18:26:32 +00:00
commit 9a696dc6bb
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=345885
385 changed files with 16879 additions and 1891 deletions

View File

@ -31,7 +31,7 @@
.\"
.\" $Begemot: gensnmptree.1 383 2006-05-30 07:40:49Z brandt_h $
.\"
.Dd June 29, 2018
.Dd April 2, 2019
.Dt GENSNMPTREE 1
.Os
.Sh NAME
@ -100,25 +100,11 @@ is the length of the OID.
is the last component of the OID.
.El
.It Fl F
Together with
.Fl E
causes
.Nm
instead of the generation of enum definitions the generation of
functions for checking a value to be one of the enumeration variants and
for conversion between strings and the enum. The file is sent to standard
output and is meant to be included into a C-file for compilation.
emit definitions for C-functions includeable in a C-file that do some basic
stuff on enums like value checking and conversion between value and strings.
.It Fl f
This flag can be used together with
.Fl E
or when generating the tree files. It causes
.Nm
to emit static inline functions for checking a value to be one of the
enumeration values and for conversion between strings and the enum.
If used when generating the tree files, the preprocessor symbol
.Ar SNMPTREE_TYPES
must be defined when including the tree header file for these definitions
to become visible.
emit definitions for inline C-functions that do some basic
stuff on enums like value checking and conversion between value and strings.
.It Fl h
Print a short help page.
.It Fl I Ar directory
@ -136,36 +122,6 @@ Instead of normal output print the resulting tree.
Prefix the file names and the table name with
.Ar prefix .
.El
.Pp
The following functions are generated by
.Fl f
or
.Fl F :
.Pp
.Ft static inline int
.Fn isok_EnumName "enum EnumName" ;
.Pp
.Ft static inline const char *
.Fn tostr_EnumName "enum EnumName" ;
.Pp
.Ft static inline int
.Fn fromstr_EnumName "const char *" "enum EnumName *" ;
.Pp
The
.Fa EnumName
is replaced with the enumeration name.
.Fn isok_EnumName
returns 1 if the argument is one of the valid enum values and 0 otherwise.
.Fn tostr_EnumName
returns a string representation of the enumeration value.
If the values is not one of the legal values
.Ar EnumName???
is returned.
.Fn fromstr_EnumName
returns 1 if the string represents one of the legal enumeration values and
0 otherwise.
If 1 is return the variable pointed to by the second argument is set to
the enumeration value.
.Sh MIBS
The syntax of the MIB description file can formally be specified as follows:
.Bd -unfilled -offset indent

View File

@ -110,7 +110,6 @@ static int debug;
static const char usgtxt[] = "\
Generate SNMP tables.\n\
$Id$\n\
usage: gensnmptree [-dEeFfhlt] [-I directory] [-i infile] [-p prefix]\n\
[name]...\n\
options:\n\
@ -127,6 +126,37 @@ options:\n\
-t generate a .def file\n\
";
/**
* Program operation.
*/
enum op {
/** generate the tree */
OP_GEN,
/** extract OIDs */
OP_EXTRACT,
/** print the parsed tree */
OP_TREE,
/** extract enums */
OP_ENUMS,
};
/**
* Which functions to create.
*/
enum gen_funcs {
/** none */
GEN_FUNCS_NONE,
/** functions for header files */
GEN_FUNCS_H,
/** functions for C files */
GEN_FUNCS_C,
};
/*
* A node in the OID tree
*/
@ -162,15 +192,18 @@ struct node {
uint32_t index; /* index for table entry */
char *func; /* function for tables */
struct node_list subs;
char *subtypes[SNMP_INDEXES_MAX];
} entry;
struct leaf {
enum snmp_syntax syntax; /* syntax for this leaf */
char *func; /* function name */
char *subtype; /* subtype */
} leaf;
struct column {
enum snmp_syntax syntax; /* syntax for this column */
char *subtype; /* subtype */
} column;
} u;
};
@ -214,7 +247,7 @@ xalloc(size_t size)
{
void *ptr;
if ((ptr = malloc(size)) == NULL)
if ((ptr = calloc(1, size)) == NULL)
err(1, "allocing %zu bytes", size);
return (ptr);
@ -710,12 +743,14 @@ make_type(const char *s)
* token.
*/
static u_int
parse_type(enum tok *tok, struct type *t, const char *vname)
parse_type(enum tok *tok, struct type *t, const char *vname, char **subtype)
{
u_int syntax;
struct enums *e;
syntax = val;
if (subtype != NULL)
*subtype = NULL;
if (*tok == TOK_ENUM || *tok == TOK_BITS) {
if (t == NULL && vname != NULL) {
@ -759,6 +794,8 @@ parse_type(enum tok *tok, struct type *t, const char *vname)
if ((*tok = gettoken()) == '|') {
if (gettoken() != TOK_STR)
report("subtype expected after '|'");
if (subtype != NULL)
*subtype = savetok();
*tok = gettoken();
}
}
@ -794,18 +831,21 @@ parse(enum tok tok)
if ((tok = gettoken()) == TOK_TYPE || tok == TOK_DEFTYPE ||
tok == TOK_ENUM || tok == TOK_BITS) {
/* LEAF or COLUM */
u_int syntax = parse_type(&tok, NULL, node->name);
char *subtype;
u_int syntax = parse_type(&tok, NULL, node->name, &subtype);
if (tok == TOK_STR) {
/* LEAF */
node->type = NODE_LEAF;
node->u.leaf.func = savetok();
node->u.leaf.syntax = syntax;
node->u.leaf.subtype = subtype;
tok = gettoken();
} else {
/* COLUMN */
node->type = NODE_COLUMN;
node->u.column.syntax = syntax;
node->u.column.subtype = subtype;
}
while (tok != ')') {
@ -825,9 +865,12 @@ parse(enum tok tok)
tok = gettoken();
while (tok == TOK_TYPE || tok == TOK_DEFTYPE ||
tok == TOK_ENUM || tok == TOK_BITS) {
u_int syntax = parse_type(&tok, NULL, node->name);
if (index_count++ == SNMP_INDEXES_MAX)
char *subtype;
u_int syntax = parse_type(&tok, NULL, node->name,
&subtype);
if (index_count == SNMP_INDEXES_MAX)
report("too many table indexes");
node->u.entry.subtypes[index_count++] = subtype;
node->u.entry.index |=
syntax << (SNMP_INDEX_SHIFT * index_count);
}
@ -882,7 +925,8 @@ parse_top(enum tok tok)
tok = gettoken();
t->is_enum = (tok == TOK_ENUM);
t->is_bits = (tok == TOK_BITS);
t->syntax = parse_type(&tok, t, NULL);
t->syntax = parse_type(&tok, t, NULL, NULL);
pushback(tok);
return (NULL);
@ -903,7 +947,7 @@ parse_top(enum tok tok)
* Generate the C-code table part for one node.
*/
static void
gen_node(FILE *fp, struct node *np, struct asn_oid *oid, u_int idx,
gen_node(FILE *fp, const struct node *np, struct asn_oid *oid, u_int idx,
const char *func)
{
u_int n;
@ -1008,7 +1052,7 @@ gen_node(FILE *fp, struct node *np, struct asn_oid *oid, u_int idx,
* Generate the header file with the function declarations.
*/
static void
gen_header(FILE *fp, struct node *np, u_int oidlen, const char *func)
gen_header(FILE *fp, const struct node *np, u_int oidlen, const char *func)
{
char f[MAXSTR + 4];
struct node *sub;
@ -1058,7 +1102,7 @@ gen_header(FILE *fp, struct node *np, u_int oidlen, const char *func)
* Generate the OID table.
*/
static void
gen_table(FILE *fp, struct node *node)
gen_table(FILE *fp, const struct node *node)
{
struct asn_oid oid;
@ -1067,7 +1111,6 @@ gen_table(FILE *fp, struct node *node)
#ifdef HAVE_STDINT_H
fprintf(fp, "#include <stdint.h>\n");
#endif
fprintf(fp, "#include <string.h>\n");
if (localincs) {
fprintf(fp, "#include \"asn1.h\"\n");
fprintf(fp, "#include \"snmp.h\"\n");
@ -1118,6 +1161,8 @@ gen_tree(const struct node *np, int level)
case NODE_LEAF:
print_syntax(np->u.leaf.syntax);
if (np->u.leaf.subtype != NULL)
printf(" | %s", np->u.leaf.subtype);
printf(" %s%s%s)\n", np->u.leaf.func,
(np->flags & FL_GET) ? " GET" : "",
(np->flags & FL_SET) ? " SET" : "");
@ -1137,8 +1182,11 @@ gen_tree(const struct node *np, int level)
case NODE_ENTRY:
printf(" :");
for (i = 0; i < SNMP_INDEX_COUNT(np->u.entry.index); i++)
for (i = 0; i < SNMP_INDEX_COUNT(np->u.entry.index); i++) {
print_syntax(SNMP_INDEX(np->u.entry.index, i));
if (np->u.entry.subtypes[i] != NULL)
printf(" | %s", np->u.entry.subtypes[i]);
}
printf(" %s\n", np->u.entry.func);
TAILQ_FOREACH(sp, &np->u.entry.subs, link)
gen_tree(sp, level + 1);
@ -1147,6 +1195,8 @@ gen_tree(const struct node *np, int level)
case NODE_COLUMN:
print_syntax(np->u.column.syntax);
if (np->u.column.subtype != NULL)
printf(" | %s", np->u.column.subtype);
printf("%s%s)\n", (np->flags & FL_GET) ? " GET" : "",
(np->flags & FL_SET) ? " SET" : "");
break;
@ -1194,15 +1244,6 @@ extract(FILE *fp, const struct node *np, struct asn_oid *oid, const char *obj,
return (1);
}
/**
* Extract the named OID.
*
* \param fp file to extract to
* \param root root of the tree
* \param object name of the object to extract
*
* \return 0 on success, -1 if the object was not found
*/
static int
gen_extract(FILE *fp, const struct node *root, char *object)
{
@ -1390,45 +1431,6 @@ unminus(FILE *fp, const char *s)
}
}
/**
* Generate a definition for the enum packed into a guard against multiple
* definitions.
*
* \param fp file to write definition to
* \param t type
*/
static void
gen_enum(FILE *fp, const struct type *t)
{
const struct enums *e;
long min = LONG_MAX;
fprintf(fp, "\n");
fprintf(fp, "#ifndef %s_defined__\n", t->name);
fprintf(fp, "#define %s_defined__\n", t->name);
fprintf(fp, "/*\n");
fprintf(fp, " * From %s:%u\n", t->from_fname, t->from_lno);
fprintf(fp, " */\n");
fprintf(fp, "enum %s {\n", t->name);
TAILQ_FOREACH(e, &t->enums, link) {
fprintf(fp, "\t%s_", t->name);
unminus(fp, e->name);
fprintf(fp, " = %ld,\n", e->value);
if (e->value < min)
min = e->value;
}
fprintf(fp, "};\n");
fprintf(fp, "#define STROFF_%s %ld\n", t->name, min);
fprintf(fp, "#define STRING_%s \\\n", t->name);
TAILQ_FOREACH(e, &t->enums, link) {
fprintf(fp, "\t[%ld] = \"%s_", e->value - min, t->name);
unminus(fp, e->name);
fprintf(fp, "\",\\\n");
}
fprintf(fp, "\n");
fprintf(fp, "#endif /* %s_defined__ */\n", t->name);
}
/**
* Generate helper functions for an enum.
*
@ -1493,6 +1495,54 @@ gen_enum_funcs(FILE *fp, const struct type *t, int ccode)
fprintf(fp, "}\n");
}
/**
* Generate a definition for the enum packed into a guard against multiple
* definitions.
*
* \param fp file to write definition to
* \param t type
* \param dof generate functions too
*/
static void
gen_enum(FILE *fp, const struct type *t, int dof)
{
const struct enums *e;
long min = LONG_MAX;
fprintf(fp, "\n");
fprintf(fp, "#ifndef %s_defined__\n", t->name);
fprintf(fp, "#define %s_defined__\n", t->name);
fprintf(fp, "/*\n");
fprintf(fp, " * From %s:%u\n", t->from_fname, t->from_lno);
fprintf(fp, " */\n");
fprintf(fp, "enum %s {\n", t->name);
TAILQ_FOREACH(e, &t->enums, link) {
fprintf(fp, "\t%s_", t->name);
unminus(fp, e->name);
fprintf(fp, " = %ld,\n", e->value);
if (e->value < min)
min = e->value;
}
fprintf(fp, "};\n");
fprintf(fp, "#define STROFF_%s %ld\n", t->name, min);
fprintf(fp, "#define STRING_%s \\\n", t->name);
TAILQ_FOREACH(e, &t->enums, link) {
fprintf(fp, "\t[%ld] = \"%s_", e->value - min, t->name);
unminus(fp, e->name);
fprintf(fp, "\",\\\n");
}
fprintf(fp, "\n");
if (dof) {
fprintf(fp, "#ifdef SNMPENUM_FUNCS\n");
fprintf(fp, "\n");
gen_enum_funcs(fp, t, 0);
fprintf(fp, "\n");
fprintf(fp, "#endif\n");
fprintf(fp, "\n");
}
fprintf(fp, "#endif /* %s_defined__ */\n", t->name);
}
/**
* Generate helper functions for an enum. This generates code for a c file.
*
@ -1529,6 +1579,16 @@ gen_all_enum_funcs(FILE *fp, int ccode)
gen_enum_funcs(fp, t, ccode);
}
static void
gen_enums(FILE *fp, int dof)
{
const struct type *t;
LIST_FOREACH(t, &types, link)
if (t->is_enum || t->is_bits)
gen_enum(fp, t, dof);
}
/**
* Extract a given enum to the specified file and optionally generate static
* inline helper functions for them.
@ -1546,9 +1606,7 @@ extract_enum(FILE *fp, const char *name, int gen_funcs)
LIST_FOREACH(t, &types, link)
if ((t->is_enum || t->is_bits) && strcmp(t->name, name) == 0) {
gen_enum(fp, t);
if (gen_funcs)
gen_enum_funcs(fp, t, 0);
gen_enum(fp, t, gen_funcs);
return (0);
}
return (-1);
@ -1567,11 +1625,8 @@ extract_all_enums(FILE *fp, int gen_funcs)
const struct type *t;
LIST_FOREACH(t, &types, link)
if (t->is_enum || t->is_bits) {
gen_enum(fp, t);
if (gen_funcs)
gen_enum_funcs(fp, t, 0);
}
if (t->is_enum || t->is_bits)
gen_enum(fp, t, gen_funcs);
}
/**
@ -1579,13 +1634,12 @@ extract_all_enums(FILE *fp, int gen_funcs)
*
* \param argc number of arguments
* \param argv arguments (enum names)
* \param gen_funcs_h generate functions into the header file
* \param gen_funcs_c generate a .c file with functions
* \param gen_funcs which functions to generate
*/
static void
make_enums(int argc, char *argv[], int gen_funcs_h, int gen_funcs_c)
make_enums(int argc, char *argv[], enum gen_funcs gen_funcs)
{
if (gen_funcs_c) {
if (gen_funcs == GEN_FUNCS_C) {
if (argc == 0)
gen_all_enum_funcs(stdout, 1);
else {
@ -1595,30 +1649,58 @@ make_enums(int argc, char *argv[], int gen_funcs_h, int gen_funcs_c)
}
} else {
if (argc == 0)
extract_all_enums(stdout, gen_funcs_h);
extract_all_enums(stdout, gen_funcs == GEN_FUNCS_H);
else {
for (int i = 0; i < argc; i++)
if (extract_enum(stdout, argv[i], gen_funcs_h))
if (extract_enum(stdout, argv[i],
gen_funcs == GEN_FUNCS_H))
errx(1, "enum not found: %s", argv[i]);
}
}
}
/**
* Produce the operation tables for the daemon or a module.
*
* \param root tree root
* \param gen_funcs generate enum funcs
*/
static void
make_table(const struct node *root, int gen_funcs)
{
FILE *fp;
char fname[MAXPATHLEN + 1];
sprintf(fname, "%stree.h", file_prefix);
if ((fp = fopen(fname, "w")) == NULL)
err(1, "%s: ", fname);
gen_header(fp, root, PREFIX_LEN, NULL);
fprintf(fp, "\n#ifdef SNMPTREE_TYPES\n");
gen_enums(fp, gen_funcs);
fprintf(fp, "\n#endif /* SNMPTREE_TYPES */\n\n");
fprintf(fp, "#define %sCTREE_SIZE %u\n", file_prefix, tree_size);
fprintf(fp, "extern const struct snmp_node %sctree[];\n", file_prefix);
fclose(fp);
sprintf(fname, "%stree.c", file_prefix);
if ((fp = fopen(fname, "w")) == NULL)
err(1, "%s: ", fname);
gen_table(fp, root);
fclose(fp);
}
int
main(int argc, char *argv[])
{
int do_extract = 0;
int do_tree = 0;
int do_enums = 0;
int gen_funcs_h = 0;
int gen_funcs_c = 0;
int opt;
struct node *root;
char fname[MAXPATHLEN + 1];
int tok;
FILE *fp;
enum op op = OP_GEN;
enum gen_funcs gen_funcs = GEN_FUNCS_NONE;
char *infile = NULL;
int opt;
while ((opt = getopt(argc, argv, "dEeFfhI:i:lp:t")) != EOF)
switch (opt) {
@ -1627,19 +1709,29 @@ main(int argc, char *argv[])
break;
case 'E':
do_enums = 1;
if (op != OP_GEN && op != OP_ENUMS)
errx(1, "-E conflicts with earlier options");
op = OP_ENUMS;
break;
case 'e':
do_extract = 1;
if (op != OP_GEN && op != OP_EXTRACT)
errx(1, "-e conflicts with earlier options");
op = OP_EXTRACT;
break;
case 'F':
gen_funcs_c = 1;
if (gen_funcs != GEN_FUNCS_NONE &&
gen_funcs != GEN_FUNCS_C)
errx(1, "-F conflicts with -f");
gen_funcs = GEN_FUNCS_C;
break;
case 'f':
gen_funcs_h = 1;
if (gen_funcs != GEN_FUNCS_NONE &&
gen_funcs != GEN_FUNCS_H)
errx(1, "-f conflicts with -F");
gen_funcs = GEN_FUNCS_H;
break;
case 'h':
@ -1666,75 +1758,61 @@ main(int argc, char *argv[])
break;
case 't':
do_tree = 1;
if (op != OP_GEN && op != OP_TREE)
errx(1, "-t conflicts with earlier options");
op = OP_TREE;
break;
}
if (do_extract + do_tree + do_enums > 1)
errx(1, "conflicting options -e/-t/-E");
if (!do_extract && !do_enums && argc != optind)
errx(1, "no arguments allowed");
if (do_extract && argc == optind)
errx(1, "no objects specified");
if ((gen_funcs_h || gen_funcs_c) && (do_extract || do_tree))
errx(1, "-f and -F not allowed with -e or -t");
if (gen_funcs_c && !do_enums)
errx(1, "-F requires -E");
if (gen_funcs_h && gen_funcs_c)
errx(1, "-f and -F are mutually exclusive");
argc -= optind;
argv += optind;
/* open input */
if (infile == NULL) {
input_new(stdin, NULL, "<stdin>");
} else {
FILE *fp;
if ((fp = fopen(infile, "r")) == NULL)
err(1, "%s", infile);
input_new(fp, NULL, infile);
}
root = parse_top(gettoken());
/* parse and check input */
struct node *root = parse_top(gettoken());
int tok;
while ((tok = gettoken()) != TOK_EOF)
merge(&root, parse_top(tok));
if (root)
check_tree(root);
if (do_extract) {
while (optind < argc) {
if (gen_extract(stdout, root, argv[optind]))
errx(1, "object not found: %s", argv[optind]);
optind++;
}
/* do what the user has requested */
switch (op) {
case OP_EXTRACT:
if (argc == 0)
errx(1, "-e requires arguments");
for (int i = 0; i < argc; i++)
if (gen_extract(stdout, root, argv[i]))
errx(1, "object not found: %s", argv[i]);
return (0);
}
if (do_enums) {
make_enums(argc - optind, argv + optind,
gen_funcs_h, gen_funcs_c);
case OP_ENUMS:
make_enums(argc, argv, gen_funcs);
return (0);
}
if (do_tree) {
case OP_TREE:
if (argc != 0)
errx(1, "-t allows no arguments");
gen_tree(root, 0);
return (0);
case OP_GEN:
if (argc != 0)
errx(1, "tree generation allows no arguments");
make_table(root, gen_funcs == GEN_FUNCS_H);
return (0);
}
sprintf(fname, "%stree.h", file_prefix);
if ((fp = fopen(fname, "w")) == NULL)
err(1, "%s: ", fname);
gen_header(fp, root, PREFIX_LEN, NULL);
fprintf(fp, "\n#ifdef SNMPTREE_TYPES\n");
extract_all_enums(fp, gen_funcs_h);
fprintf(fp, "\n#endif /* SNMPTREE_TYPES */\n\n");
fprintf(fp, "#define %sCTREE_SIZE %u\n", file_prefix, tree_size);
fprintf(fp, "extern const struct snmp_node %sctree[];\n", file_prefix);
fclose(fp);
sprintf(fname, "%stree.c", file_prefix);
if ((fp = fopen(fname, "w")) == NULL)
err(1, "%s: ", fname);
gen_table(fp, root);
fclose(fp);
return (0);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2005
* Copyright (c) 2004-2005,2018
* Hartmut Brandt.
* All rights reserved.
* Copyright (c) 2001-2003
@ -34,11 +34,13 @@
*
* Support functions for SNMP clients.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <net/if.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
@ -58,12 +60,16 @@
#include <err.h>
#endif
#include <arpa/inet.h>
#include "support.h"
#include "asn1.h"
#include "snmp.h"
#include "snmpclient.h"
#include "snmppriv.h"
#define DEBUG_PARSE 0
/* global context */
struct snmp_client snmp_client;
@ -924,7 +930,8 @@ open_client_udp(const char *host, const char *port)
/* open connection */
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
hints.ai_family = snmp_client.trans == SNMP_TRANS_UDP ? AF_INET:
AF_INET6;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
error = getaddrinfo(snmp_client.chost, snmp_client.cport, &hints, &res0);
@ -1068,6 +1075,7 @@ snmp_open(const char *host, const char *port, const char *readcomm,
switch (snmp_client.trans) {
case SNMP_TRANS_UDP:
case SNMP_TRANS_UDP6:
if (open_client_udp(host, port) != 0)
return (-1);
break;
@ -1866,99 +1874,412 @@ snmp_client_set_port(struct snmp_client *cl, const char *p)
return (0);
}
/*
* parse a server specification
static const char *const trans_list[] = {
[SNMP_TRANS_UDP] = "udp::",
[SNMP_TRANS_LOC_DGRAM] = "dgram::",
[SNMP_TRANS_LOC_STREAM] = "stream::",
[SNMP_TRANS_UDP6] = "udp6::",
};
/**
* Try to get a transport identifier which is a leading alphanumeric string
* terminated by a double colon. The string may not be empty. The transport
* identifier is optional.
*
* [trans::][community@][server][:port]
* \param sc client struct to set errors
* \param strp possible start of transport; updated to point to
* the next character to parse
*
* \return transport identifier
*/
static inline int
get_transp(struct snmp_client *sc, const char **strp)
{
const char *p;
size_t i;
for (i = 0; i < nitems(trans_list); i++) {
if (trans_list[i] == NULL || *trans_list[i] == '\0')
continue;
p = strstr(*strp, trans_list[i]);
if (p == *strp) {
*strp += strlen(trans_list[i]);
return ((int)i);
}
}
p = *strp;
if (p[0] == ':' && p[1] == ':') {
seterr(sc, "empty transport specifier");
return (-1);
}
/* by default assume UDP */
return (SNMP_TRANS_UDP);
}
/**
* Try to get community string. Eat everything up to the last @ (if there is
* any) but only if it is not longer than SNMP_COMMUNITY_MAXLEN. Empty
* community strings are legal.
*
* \param sc client struct to set errors
* \param strp possible start of community; updated to the point to
* the next character to parse
*
* \return end of community; equals *strp if there is none; NULL if there
* was an error
*/
static inline const char *
get_comm(struct snmp_client *sc, const char **strp)
{
const char *p = strrchr(*strp, '@');
if (p == NULL)
/* no community string */
return (*strp);
if (p - *strp > SNMP_COMMUNITY_MAXLEN) {
seterr(sc, "community string too long '%.*s'",
p - *strp, *strp);
return (NULL);
}
*strp = p + 1;
return (p);
}
/**
* Try to get an IPv6 address. This starts with an [ and should end with an ]
* and everything between should be not longer than INET6_ADDRSTRLEN and
* parseable by inet_pton().
*
* \param sc client struct to set errors
* \param strp possible start of IPv6 address (the '['); updated to point to
* the next character to parse (the one after the closing ']')
*
* \return end of address (equals *strp + 1 if there is none) or NULL
* on errors
*/
static inline const char *
get_ipv6(struct snmp_client *sc, const char **strp)
{
char str[INET6_ADDRSTRLEN + IF_NAMESIZE];
struct addrinfo hints, *res;
int error;
if (**strp != '[')
return (*strp + 1);
const char *p = *strp + 1;
while (*p != ']' ) {
if (*p == '\0') {
seterr(sc, "unterminated IPv6 address '%.*s'",
p - *strp, *strp);
return (NULL);
}
p++;
}
if (p - *strp > INET6_ADDRSTRLEN + IF_NAMESIZE) {
seterr(sc, "IPv6 address too long '%.*s'", p - *strp, *strp);
return (NULL);
}
strncpy(str, *strp + 1, p - (*strp + 1));
str[p - (*strp + 1)] = '\0';
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME | AI_NUMERICHOST;
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
error = getaddrinfo(str, NULL, &hints, &res);
if (error != 0) {
seterr(sc, "%s: %s", str, gai_strerror(error));
return (NULL);
}
freeaddrinfo(res);
*strp = p + 1;
return (p);
}
/**
* Try to get an IPv4 address. This starts with a digit and consists of digits
* and dots, is not longer INET_ADDRSTRLEN and must be parseable by
* inet_aton().
*
* \param sc client struct to set errors
* \param strp possible start of IPv4 address; updated to point to the
* next character to parse
*
* \return end of address (equals *strp if there is none) or NULL
* on errors
*/
static inline const char *
get_ipv4(struct snmp_client *sc, const char **strp)
{
const char *p = *strp;
while (isascii(*p) && (isdigit(*p) || *p == '.'))
p++;
if (p - *strp > INET_ADDRSTRLEN) {
seterr(sc, "IPv4 address too long '%.*s'", p - *strp, *strp);
return (NULL);
}
if (*strp == p)
return *strp;
char str[INET_ADDRSTRLEN + 1];
strncpy(str, *strp, p - *strp);
str[p - *strp] = '\0';
struct in_addr addr;
if (inet_aton(str, &addr) != 1) {
seterr(sc, "illegal IPv4 address '%s'", str);
return (NULL);
}
*strp = p;
return (p);
}
/**
* Try to get a hostname. This includes everything up to but not including
* the last colon (if any). There is no length restriction.
*
* \param sc client struct to set errors
* \param strp possible start of hostname; updated to point to the next
* character to parse (the trailing NUL character or the last
* colon)
*
* \return end of address (equals *strp if there is none)
*/
static inline const char *
get_host(struct snmp_client *sc __unused, const char **strp)
{
const char *p = strrchr(*strp, ':');
if (p == NULL) {
*strp += strlen(*strp);
return (*strp);
}
*strp = p;
return (p);
}
/**
* Try to get a port number. This start with a colon and extends to the end
* of string. The port number must not be empty.
*
* \param sc client struct to set errors
* \param strp possible start of port specification; if this points to a
* colon there is a port specification
*
* \return end of port number (equals *strp if there is none); NULL
* if there is no port number
*/
static inline const char *
get_port(struct snmp_client *sc, const char **strp)
{
if (**strp != ':')
return (*strp + 1);
if ((*strp)[1] == '\0') {
seterr(sc, "empty port name");
return (NULL);
}
*strp += strlen(*strp);
return (*strp);
}
/**
* Save the string in the range given by two pointers.
*
* \param sc client struct to set errors
* \param s begin and end pointers
*
* \return freshly allocated copy of the string between s[0] and s[1]
*/
static inline char *
save_str(struct snmp_client *sc, const char *const s[2])
{
char *m;
if ((m = malloc(s[1] - s[0] + 1)) == NULL) {
seterr(sc, "%s: %s", __func__, strerror(errno));
return (NULL);
}
strncpy(m, s[0], s[1] - s[0]);
m[s[1] - s[0]] = '\0';
return (m);
}
/**
* Parse a server specification. All parts are optional:
*
* [<trans>::][<comm>@][<host-or-ip>][:<port>]
*
* The transport string consists of letters, digits or '_' and starts with
* a letter or digit. It is terminated by two colons and may not be empty.
*
* The community string is terminated by the last '@' and does not exceed
* SNMP_COMMUNITY_MAXLEN. It may be empty.
*
* The host or ip is either an IPv4 address (as parsed by inet_pton()), an
* IPv6 address in '[' and ']' and parseable by inet_aton() or a hostname
* terminated by the last colon or by the NUL character.
*
* The port number may be specified numerically or symbolically and starts
* with the last colon.
*
* The functions sets the chost, cport, trans, read_community and
* write_community fields on success and the error field on errors.
* The chost and cport fields are allocated by malloc(3), their previous
* content is deallocated by free(3).
*
* The function explicitly allows mismatches between the transport and
* the address type in order to support IPv4 in IPv6 addresses.
*
* \param sc client struct to fill
* \param str string to parse
*
* \return 0 on success and -1 on errors
*/
int
snmp_parse_server(struct snmp_client *sc, const char *str)
{
const char *p, *s = str;
const char *const orig = str;
/* parse input */
int i, trans = get_transp(sc, &str);
if (trans < 0)
return (-1);
/* choose automatically */
i = orig == str ? -1: trans;
/* look for a double colon */
for (p = s; *p != '\0'; p++) {
if (*p == '\\' && p[1] != '\0') {
p++;
continue;
}
if (*p == ':' && p[1] == ':')
break;
}
if (*p != '\0') {
if (p > s) {
if (p - s == 3 && strncmp(s, "udp", 3) == 0)
sc->trans = SNMP_TRANS_UDP;
else if (p - s == 6 && strncmp(s, "stream", 6) == 0)
sc->trans = SNMP_TRANS_LOC_STREAM;
else if (p - s == 5 && strncmp(s, "dgram", 5) == 0)
sc->trans = SNMP_TRANS_LOC_DGRAM;
else {
seterr(sc, "unknown SNMP transport '%.*s'",
(int)(p - s), s);
return (-1);
}
}
s = p + 2;
const char *const comm[2] = {
str,
get_comm(sc, &str),
};
if (comm[1] == NULL)
return (-1);
const char *const ipv6[2] = {
str + 1,
get_ipv6(sc, &str),
};
if (ipv6[1] == NULL)
return (-1);
const char *ipv4[2] = {
str,
str,
};
const char *host[2] = {
str,
str,
};
if (ipv6[0] == ipv6[1]) {
ipv4[1] = get_ipv4(sc, &str);
if (ipv4[0] == ipv4[1])
host[1] = get_host(sc, &str);
}
/* look for a @ */
for (p = s; *p != '\0'; p++) {
if (*p == '\\' && p[1] != '\0') {
p++;
continue;
}
if (*p == '@')
break;
const char *port[2] = {
str + 1,
get_port(sc, &str),
};
if (port[1] == NULL)
return (-1);
if (*str != '\0') {
seterr(sc, "junk at end of server specification '%s'", str);
return (-1);
}
if (*p != '\0') {
if (p - s > SNMP_COMMUNITY_MAXLEN) {
seterr(sc, "community string too long");
#if DEBUG_PARSE
printf("transp: %u\n", trans);
printf("comm: %zu %zu\n", comm[0] - orig, comm[1] - orig);
printf("ipv6: %zu %zu\n", ipv6[0] - orig, ipv6[1] - orig);
printf("ipv4: %zu %zu\n", ipv4[0] - orig, ipv4[1] - orig);
printf("host: %zu %zu\n", host[0] - orig, host[1] - orig);
printf("port: %zu %zu\n", port[0] - orig, port[1] - orig);
#endif
/* analyse and allocate */
char *chost;
if (ipv6[0] != ipv6[1]) {
if ((chost = save_str(sc, ipv6)) == NULL)
return (-1);
}
strncpy(sc->read_community, s, p - s);
sc->read_community[p - s] = '\0';
strncpy(sc->write_community, s, p - s);
sc->write_community[p - s] = '\0';
s = p + 1;
}
/* look for a colon */
for (p = s; *p != '\0'; p++) {
if (*p == '\\' && p[1] != '\0') {
p++;
continue;
}
if (*p == ':')
break;
}
if (*p == ':') {
if (p > s) {
/* host:port */
free(sc->chost);
if ((sc->chost = malloc(p - s + 1)) == NULL) {
seterr(sc, "%s", strerror(errno));
return (-1);
}
strncpy(sc->chost, s, p - s);
sc->chost[p - s] = '\0';
}
/* port */
free(sc->cport);
if ((sc->cport = strdup(p + 1)) == NULL) {
seterr(sc, "%s", strerror(errno));
if (i == -1 || trans == SNMP_TRANS_UDP)
trans = SNMP_TRANS_UDP6;
} else if (ipv4[0] != ipv4[1]) {
if ((chost = save_str(sc, ipv4)) == NULL)
return (-1);
}
} else if (p > s) {
/* host */
free(sc->chost);
if ((sc->chost = strdup(s)) == NULL) {
seterr(sc, "%s", strerror(errno));
if (i == -1)
trans = SNMP_TRANS_UDP;
} else {
if ((chost = save_str(sc, host)) == NULL)
return (-1);
if (i == -1) {
/*
* Default transport is UDP unless the host contains
* a slash in which case we default to DGRAM.
*/
for (const char *p = host[0]; p < host[1]; p++)
if (*p == '/') {
trans = SNMP_TRANS_LOC_DGRAM;
break;
}
}
}
char *cport;
if (port[0] == port[1] && (
trans == SNMP_TRANS_UDP || trans == SNMP_TRANS_UDP6)) {
/* If port was not specified, use "snmp" name by default */
cport = strdup("snmp");
} else
cport = save_str(sc, port);
if (cport == NULL) {
free(chost);
return (-1);
}
/* commit */
sc->trans = trans;
/*
* If community string was specified and it is empty, overwrite it.
* If it was not specified, use default.
*/
if (comm[0] != comm[1] || strrchr(comm[0], '@') != NULL) {
strncpy(sc->read_community, comm[0], comm[1] - comm[0]);
sc->read_community[comm[1] - comm[0]] = '\0';
strncpy(sc->write_community, comm[0], comm[1] - comm[0]);
sc->write_community[comm[1] - comm[0]] = '\0';
}
free(sc->chost);
sc->chost = chost;
free(sc->cport);
sc->cport = cport;
#if DEBUG_PARSE
printf("Committed values:\n");
printf("trans: %u\n", sc->trans);
printf("comm: '%s'/'%s'\n", sc->read_community, sc->write_community);
printf("host: '%s'\n", sc->chost);
printf("port: '%s'\n", sc->cport);
#endif
return (0);
}

View File

@ -49,6 +49,7 @@
#define SNMP_TRANS_UDP 0
#define SNMP_TRANS_LOC_DGRAM 1
#define SNMP_TRANS_LOC_STREAM 2
#define SNMP_TRANS_UDP6 3
/* type of callback function for responses
* this callback function is responsible for free() any memory associated with

View File

@ -46,3 +46,11 @@ typedef StorageType ENUM (
5 readOnly
)
typedef InetAddressType ENUM (
0 unknown
1 ipv4
2 ipv6
3 ipv4z
4 ipv6z
16 dns
)

View File

@ -373,11 +373,6 @@ op_ifxtable(struct snmp_context *ctx, struct snmp_value *value,
switch (op) {
again:
if (op != SNMP_OP_GETNEXT)
return (SNMP_ERR_NOSUCHNAME);
/* FALLTHROUGH */
case SNMP_OP_GETNEXT:
if ((ifp = NEXT_OBJECT_INT(&mibif_list, &value->var, sub)) == NULL)
return (SNMP_ERR_NOSUCHNAME);
@ -460,52 +455,36 @@ op_ifxtable(struct snmp_context *ctx, struct snmp_value *value,
break;
case LEAF_ifHCInOctets:
if (!(ifp->flags & MIBIF_HIGHSPEED))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_inoctets;
break;
case LEAF_ifHCInUcastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_ipackets -
MIBIF_PRIV(ifp)->hc_imcasts;
break;
case LEAF_ifHCInMulticastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_imcasts;
break;
case LEAF_ifHCInBroadcastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = 0;
break;
case LEAF_ifHCOutOctets:
if (!(ifp->flags & MIBIF_HIGHSPEED))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_outoctets;
break;
case LEAF_ifHCOutUcastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_opackets -
MIBIF_PRIV(ifp)->hc_omcasts;
break;
case LEAF_ifHCOutMulticastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = MIBIF_PRIV(ifp)->hc_omcasts;
break;
case LEAF_ifHCOutBroadcastPkts:
if (!(ifp->flags & (MIBIF_VERYHIGHSPEED|MIBIF_HIGHSPEED)))
goto again;
value->v.counter64 = 0;
break;

View File

@ -3,6 +3,10 @@
-- Fraunhofer Institute for Open Communication Systems (FhG Fokus).
-- All rights reserved.
--
-- Copyright (c) 2018
-- Hartmut Brandt.
-- All rights reserved.
--
-- Author: Harti Brandt <harti@freebsd.org>
--
-- Redistribution and use in source and binary forms, with or without
@ -34,17 +38,17 @@ BEGEMOT-SNMPD-MIB DEFINITIONS ::= BEGIN
IMPORTS
MODULE-IDENTITY, OBJECT-TYPE, OBJECT-IDENTITY, Counter32,
Unsigned32, IpAddress
Integer32, Unsigned32, IpAddress
FROM SNMPv2-SMI
TEXTUAL-CONVENTION, TruthValue, RowStatus
FROM SNMPv2-TC
MODULE-COMPLIANCE, OBJECT-GROUP
FROM SNMPv2-CONF
InetAddressType, InetAddress, InetPortNumber
FROM INET-ADDRESS-MIB
begemot
FROM BEGEMOT-MIB;
begemotSnmpd MODULE-IDENTITY
LAST-UPDATED "201801190000Z"
LAST-UPDATED "201808080000Z"
ORGANIZATION "Fraunhofer FOKUS, CATS"
CONTACT-INFO
" Hartmut Brandt
@ -59,6 +63,9 @@ begemotSnmpd MODULE-IDENTITY
E-mail: harti@freebsd.org"
DESCRIPTION
"The MIB module for the Begemot SNMP daemon."
REVISION "201808080000Z"
DESCRIPTION
"Add the begemotSnmpdTransInetTable."
::= { begemot 1 }
begemotSnmpdObjects OBJECT IDENTIFIER ::= { begemotSnmpd 1 }
@ -93,7 +100,7 @@ begemotSnmpdAgentFreeBSD OBJECT-IDENTITY
begemotSnmpdConfig OBJECT IDENTIFIER ::= { begemotSnmpdObjects 1 }
begemotSnmpdTransmitBuffer OBJECT-TYPE
SYNTAX INTEGER (484..65535)
SYNTAX Integer32 (484..65535)
MAX-ACCESS read-write
STATUS current
DESCRIPTION
@ -103,7 +110,7 @@ begemotSnmpdTransmitBuffer OBJECT-TYPE
::= { begemotSnmpdConfig 1 }
begemotSnmpdReceiveBuffer OBJECT-TYPE
SYNTAX INTEGER (484..65535)
SYNTAX Integer32 (484..65535)
MAX-ACCESS read-write
STATUS current
DESCRIPTION
@ -181,7 +188,7 @@ begemotTrapSinkAddr OBJECT-TYPE
::= { begemotTrapSinkEntry 1 }
begemotTrapSinkPort OBJECT-TYPE
SYNTAX INTEGER (1..65535)
SYNTAX Integer32 (1..65535)
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
@ -203,7 +210,7 @@ begemotTrapSinkStatus OBJECT-TYPE
begemotSnmpdPortTable OBJECT-TYPE
SYNTAX SEQUENCE OF BegemotSnmpdPortEntry
MAX-ACCESS not-accessible
STATUS current
STATUS deprecated
DESCRIPTION
"A table with descriptions of UDP ports to listen on
for SNMP messages."
@ -212,7 +219,7 @@ begemotSnmpdPortTable OBJECT-TYPE
begemotSnmpdPortEntry OBJECT-TYPE
SYNTAX BegemotSnmpdPortEntry
MAX-ACCESS not-accessible
STATUS current
STATUS deprecated
DESCRIPTION
"An entry in the table with descriptions of UDP ports to
listen on for SNMP messages."
@ -228,15 +235,15 @@ BegemotSnmpdPortEntry ::= SEQUENCE {
begemotSnmpdPortAddress OBJECT-TYPE
SYNTAX IpAddress
MAX-ACCESS not-accessible
STATUS current
STATUS deprecated
DESCRIPTION
"The IP address to bind to."
::= { begemotSnmpdPortEntry 1 }
begemotSnmpdPortPort OBJECT-TYPE
SYNTAX INTEGER (1..65535)
SYNTAX Integer32 (1..65535)
MAX-ACCESS not-accessible
STATUS current
STATUS deprecated
DESCRIPTION
"The UDP port to listen on for SNMP messages."
::= { begemotSnmpdPortEntry 2 }
@ -244,7 +251,7 @@ begemotSnmpdPortPort OBJECT-TYPE
begemotSnmpdPortStatus OBJECT-TYPE
SYNTAX INTEGER { valid(1), invalid(2) }
MAX-ACCESS read-create
STATUS current
STATUS deprecated
DESCRIPTION
"Set status to 1 to create entry, set it to 2 to delete it."
::= { begemotSnmpdPortEntry 3 }
@ -275,7 +282,7 @@ BegemotSnmpdCommunityEntry ::= SEQUENCE {
begemotSnmpdCommunityIndex Unsigned32,
begemotSnmpdCommunityString OCTET STRING,
begemotSnmpdCommunityDescr OCTET STRING,
begemotSnmpdCommunityPermission INTEGER
begemotSnmpdCommunityPermission Unsigned32
}
begemotSnmpdCommunityModule OBJECT-TYPE
@ -312,7 +319,7 @@ begemotSnmpdCommunityDescr OBJECT-TYPE
::= { begemotSnmpdCommunityEntry 4 }
begemotSnmpdCommunityPermission OBJECT-TYPE
SYNTAX INTEGER (1..4294967295)
SYNTAX Unsigned32 (1..4294967295)
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
@ -449,7 +456,7 @@ begemotSnmpdDebugSnmpTrace OBJECT-TYPE
::= { begemotSnmpdDebug 2 }
begemotSnmpdDebugSyslogPri OBJECT-TYPE
SYNTAX INTEGER (0..8)
SYNTAX Integer32 (0..8)
MAX-ACCESS read-write
STATUS current
DESCRIPTION
@ -570,10 +577,115 @@ begemotSnmpdTransportOid OBJECT-TYPE
"A pointer to the group with the transport-dependend stuff."
::= { begemotSnmpdTransportEntry 3 }
-- ----------------------------------------------------------------------
--
-- Internet port table.
--
BegemotSnmpdTransportProto ::= TEXTUAL-CONVENTION
STATUS current
DESCRIPTION
"A value that represents the type of protocol to be used for
listening on a socket. The following protocols are currently
used:
udp(1) Use UDP for IPv4 and IPv6 sockets."
SYNTAX INTEGER {
udp(1)
}
begemotSnmpdTransInetTable OBJECT-TYPE
SYNTAX SEQUENCE OF BegemotSnmpdTransInetEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"This table contains all the ports the daemon should listen on.
Entries can be created at initialization time via the config
file or at run time via a SET. One row can map to several open
sockets in the case of InetAddressType::dns rows. These rows
open one socket for each address returned by getaddrinfo(3).
for SNMP messages."
::= { begemotSnmpdObjects 11 }
begemotSnmpdTransInetEntry OBJECT-TYPE
SYNTAX BegemotSnmpdTransInetEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A row of the internet port table. Each row may map to one or
more listening sockets."
INDEX {
begemotSnmpdTransInetAddressType,
begemotSnmpdTransInetAddress,
begemotSnmpdTransInetPort,
begemotSnmpdTransInetProto
}
::= { begemotSnmpdTransInetTable 1 }
BegemotSnmpdTransInetEntry ::= SEQUENCE {
begemotSnmpdTransInetAddressType InetAddressType,
begemotSnmpdTransInetAddress InetAddress,
begemotSnmpdTransInetPort InetPortNumber,
begemotSnmpdTransInetProto BegemotSnmpdTransportProto,
begemotSnmpdTransInetStatus RowStatus
}
begemotSnmpdTransInetAddressType OBJECT-TYPE
SYNTAX InetAddressType
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"The type of the address. Only ipv4, ipv6, ipv6z and dns are
supported."
::= { begemotSnmpdTransInetEntry 1 }
begemotSnmpdTransInetAddress OBJECT-TYPE
SYNTAX InetAddress (SIZE (0..64))
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"The address. For ipv4 addresses the length must be 4, ipv6
addresses have a length of 16 and ipv6z addresses a length of
20 where the last four bytes are the interface index in big
endian format. dns addresses may be of zero-length in which case
getaddrinfo() generates INADDR_ANY and its IPv6 equivalent. dns
addresses will open a socket for all addresses returned by
getaddrinfo()."
::= { begemotSnmpdTransInetEntry 2 }
begemotSnmpdTransInetPort OBJECT-TYPE
SYNTAX InetPortNumber (1..65535)
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"The port to listen on for SNMP messages."
::= { begemotSnmpdTransInetEntry 3 }
begemotSnmpdTransInetProto OBJECT-TYPE
SYNTAX BegemotSnmpdTransportProto
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"The protocol to use. Currently only the value udp(1) is supported."
::= { begemotSnmpdTransInetEntry 4 }
begemotSnmpdTransInetStatus OBJECT-TYPE
SYNTAX RowStatus
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The status of the conceptual row. A row may be created using
createAndGo(4) or createAndWait(5). An inactive row can be
activated writing active(1) and an active row can be inactivated
by writing notInService(2). Finally active or inactive rows can be
deleted by writing the value destroy(6). The value of this field
will never read as notReady(3)."
::= { begemotSnmpdTransInetEntry 5 }
--
-- XXX These should go into their own MIB
--
begemotSnmpdTransUdp OBJECT IDENTIFIER ::= { begemotSnmpdTransportMappings 2 }
begemotSnmpdTransLsock OBJECT IDENTIFIER ::= { begemotSnmpdTransportMappings 3 }
begemotSnmpdTransInet OBJECT IDENTIFIER ::= { begemotSnmpdTransportMappings 4 }
END

View File

@ -65,6 +65,8 @@
#include "tree.h"
#include "oid.h"
#include "trans_inet.h"
#define PATH_PID "/var/run/%s.pid"
#define PATH_CONFIG "/etc/%s.config"
#define PATH_ENGINE "/var/%s.engine"
@ -1038,7 +1040,7 @@ snmpd_input(struct port_input *pi, struct tport *tport)
ssize_t ret, slen;
int32_t vi;
#ifdef USE_TCPWRAPPERS
char client[16];
char client[INET6_ADDRSTRLEN];
#endif
ret = tport->transport->vtab->recv(tport, pi);
@ -1184,8 +1186,12 @@ snmpd_input(struct port_input *pi, struct tport *tport)
sndbuf, &sndlen, "SNMP", ierr, vi, NULL);
if (ferr == SNMPD_INPUT_OK) {
slen = tport->transport->vtab->send(tport, sndbuf, sndlen,
pi->peer, pi->peerlen);
if (tport->transport->vtab->send != NULL)
slen = tport->transport->vtab->send(tport, sndbuf,
sndlen, pi->peer, pi->peerlen);
else
slen = tport->transport->vtab->send2(tport, sndbuf,
sndlen, pi);
if (slen == -1)
syslog(LOG_ERR, "send*: %m");
else if ((size_t)slen != sndlen)
@ -1201,7 +1207,8 @@ snmpd_input(struct port_input *pi, struct tport *tport)
}
/*
* Send a PDU to a given port
* Send a PDU to a given port. If this is a multi-socket port, use the
* first socket.
*/
void
snmp_send_port(void *targ, const struct asn_oid *port, struct snmp_pdu *pdu,
@ -1224,7 +1231,10 @@ snmp_send_port(void *targ, const struct asn_oid *port, struct snmp_pdu *pdu,
snmp_output(pdu, sndbuf, &sndlen, "SNMP PROXY");
len = trans->vtab->send(tp, sndbuf, sndlen, addr, addrlen);
if (trans->vtab->send != NULL)
len = trans->vtab->send(tp, sndbuf, sndlen, addr, addrlen);
else
len = trans->vtab->send2(tp, sndbuf, sndlen, NULL);
if (len == -1)
syslog(LOG_ERR, "sendto: %m");
@ -1238,16 +1248,37 @@ snmp_send_port(void *targ, const struct asn_oid *port, struct snmp_pdu *pdu,
/*
* Close an input source
*
* \param pi input instance
*/
void
snmpd_input_close(struct port_input *pi)
{
if (pi->id != NULL)
if (pi->id != NULL) {
fd_deselect(pi->id);
if (pi->fd >= 0)
pi->id = NULL;
}
if (pi->fd >= 0) {
(void)close(pi->fd);
if (pi->buf != NULL)
pi->fd = -1;
}
if (pi->buf != NULL) {
free(pi->buf);
pi->buf = NULL;
}
}
/*
* Initialize an input source.
*
* \param pi input instance
*/
void
snmpd_input_init(struct port_input *pi)
{
pi->id = NULL;
pi->fd = -1;
pi->buf = NULL;
}
/*
@ -1633,6 +1664,8 @@ main(int argc, char *argv[])
syslog(LOG_WARNING, "cannot start UDP transport");
if (lsock_trans.start() != SNMP_ERR_NOERROR)
syslog(LOG_WARNING, "cannot start LSOCK transport");
if (inet_trans.start() != SNMP_ERR_NOERROR)
syslog(LOG_WARNING, "cannot start INET transport");
#ifdef USE_LIBBEGEMOT
if (debug.evdebug > 0)

View File

@ -72,8 +72,18 @@ begemotSnmpdCommunityString.0.1 = $(read)
begemotSnmpdCommunityDisable = 1
# open standard SNMP ports
begemotSnmpdPortStatus.[$(host)].161 = 1
begemotSnmpdPortStatus.127.0.0.1.161 = 1
# begemotSnmpdPortStatus.[$(host)].161 = 1
# begemotSnmpdPortStatus.127.0.0.1.161 = 1
# UDP over IPv4: 127.0.0.1:161
begemotSnmpdTransInetStatus.1.4.127.0.0.1.161.1 = 4
# UDP over IPv6: ::1:161
begemotSnmpdTransInetStatus.2.16.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.161.1 = 4
# Use domain name and IPv6 link-local address with scope zone id as address
# begemotSnmpdTransInetStatus.16."localhost".161.1 = 4
# begemotSnmpdTransInetStatus.16."fe80::1%em0".161.1 = 4
# open a unix domain socket
begemotSnmpdLocalPortStatus."/var/run/snmpd.sock" = 1

View File

@ -174,7 +174,7 @@ TAILQ_HEAD(tport_list, tport);
int snmpd_input(struct port_input *, struct tport *);
void snmpd_input_close(struct port_input *);
void snmpd_input_init(struct port_input *);
/*
* Transport domain
@ -194,6 +194,10 @@ struct transport_def {
ssize_t (*send)(struct tport *, const u_char *, size_t,
const struct sockaddr *, size_t);
ssize_t (*recv)(struct tport *, struct port_input *);
/** send via a multi-socket port */
ssize_t (*send2)(struct tport *, const u_char *, size_t,
struct port_input *);
};
struct transport {
struct asn_oid index; /* transport table index */

View File

@ -56,6 +56,48 @@
* ordering can be done either on an integer/unsigned field, an asn_oid
* or an ordering function.
*/
/*
* First set of macros is used when the link is embedded into sub-struct
* and links these sub-structs. The sub-struct must be the first field.
*
* The list is a list of the subfield types.
*/
#define INSERT_OBJECT_OID_LINK_INDEX_TYPE(PTR, LIST, LINK, INDEX, SUBF) do {\
typedef __typeof ((PTR)->SUBF) _subf_type; \
_subf_type *_lelem; \
\
TAILQ_FOREACH(_lelem, (LIST), LINK) \
if (asn_compare_oid(&_lelem->INDEX, &(PTR)->SUBF.INDEX) > 0)\
break; \
if (_lelem == NULL) \
TAILQ_INSERT_TAIL((LIST), &(PTR)->SUBF, LINK); \
else \
TAILQ_INSERT_BEFORE(_lelem, &(PTR)->SUBF, LINK); \
} while (0)
#define NEXT_OBJECT_OID_LINK_INDEX_TYPE(LIST, OID, SUB, LINK, INDEX, TYPE) ({\
__typeof (TAILQ_FIRST((LIST))) _lelem; \
\
TAILQ_FOREACH(_lelem, (LIST), LINK) \
if (index_compare(OID, SUB, &_lelem->INDEX) < 0) \
break; \
(TYPE *)(_lelem); \
})
#define FIND_OBJECT_OID_LINK_INDEX_TYPE(LIST, OID, SUB, LINK, INDEX, TYPE) ({\
__typeof (TAILQ_FIRST((LIST))) _lelem; \
\
TAILQ_FOREACH(_lelem, (LIST), LINK) \
if (index_compare(OID, SUB, &_lelem->INDEX) == 0) \
break; \
(TYPE *)(_lelem); \
})
/*
* This set of macros allows specification of the link and index name.
* The index is an OID.
*/
#define INSERT_OBJECT_OID_LINK_INDEX(PTR, LIST, LINK, INDEX) do { \
__typeof (PTR) _lelem; \
\

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2018
* Hartmut Brandt.
* All rights reserved.
*
* Author: Harti Brandt <harti@freebsd.org>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#ifndef trans_inet_h_1530971397
#define trans_inet_h_1530971397
/* transport declaration */
extern const struct transport_def inet_trans;
#endif

View File

@ -70,7 +70,8 @@ const struct transport_def lsock_trans = {
lsock_close_port,
lsock_init_port,
lsock_send,
lsock_recv
lsock_recv,
NULL
};
static struct transport *my_trans;

View File

@ -67,7 +67,8 @@ const struct transport_def udp_trans = {
udp_close_port,
udp_init_port,
udp_send,
udp_recv
udp_recv,
NULL
};
static struct transport *my_trans;

View File

@ -726,8 +726,7 @@ target_activate_address(struct target_address *addrs)
sa.sin_addr.s_addr = htonl((addrs->address[0] << 24) |
(addrs->address[1] << 16) | (addrs->address[2] << 8) |
(addrs->address[3] << 0));
sa.sin_port = htons(addrs->address[4]) << 8 |
htons(addrs->address[5]) << 0;
sa.sin_port = htons(addrs->address[4] << 8 | addrs->address[5]);
if (connect(addrs->socket, (struct sockaddr *)&sa, sa.sin_len) == -1) {
syslog(LOG_ERR, "connect(%s,%u): %m",

View File

@ -3,6 +3,10 @@
# Fraunhofer Institute for Open Communication Systems (FhG Fokus).
# All rights reserved.
#
# Copyright (c) 2018
# Hartmut Brandt.
# All rights reserved.
#
# Author: Harti Brandt <harti@freebsd.org>
#
# Redistribution and use in source and binary forms, with or without
@ -33,6 +37,10 @@
include "tc.def"
typedef BegemotSnmpdTransportProto ENUM (
1 udp
)
(1 internet
(2 mgmt
(1 mibII
@ -172,13 +180,24 @@ include "tc.def"
))
(2 begemotSnmpdTransUdp OID op_transport_dummy)
(3 begemotSnmpdTransLsock OID op_transport_dummy)
(4 begemotSnmpdTransInet OID op_transport_dummy)
)
(11 begemotSnmpdTransInetTable
(1 begemotSnmpdTransInetEntry : INTEGER OCTETSTRING INTEGER INTEGER op_snmp_trans_inet
(1 begemotSnmpdTransInetAddressType InetAddressType)
(2 begemotSnmpdTransInetAddress OCTETSTRING)
(3 begemotSnmpdTransInetPort INTEGER)
(4 begemotSnmpdTransInetProto BegemotSnmpdTransportProto)
(5 begemotSnmpdTransInetStatus RowStatus GET SET)
))
)
(2 begemotSnmpdDefs
(1 begemotSnmpdAgent
(1 begemotSnmpdAgentFreeBSD OID op_dummy)
)
)
(3 begemotSnmpdCompliance)
)
))
)

19
contrib/capsicum-test/.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
capsicum-test
mini-me
mini-me.noexec
mini-me.setuid
mini-me.32
mini-me.x32
mini-me.64
libgtest.a
smoketest
*.o
libcap*.deb
libcap*.dsc
libcap*.tar.gz
libcap*.changes
casper*.deb
casper*.dsc
casper*.tar.gz
casper*.changes
libcaprights.a

View File

@ -0,0 +1,20 @@
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate

View File

@ -0,0 +1,78 @@
OS:=$(shell uname)
# Set ARCH to 32 or x32 for i386/x32 ABIs
ARCH?=64
ARCHFLAG=-m$(ARCH)
ifeq ($(OS),Linux)
PROCESSOR:=$(shell uname -p)
ifneq ($(wildcard /usr/lib/$(PROCESSOR)-linux-gnu),)
# Can use standard Debian location for static libraries.
PLATFORM_LIBDIR=/usr/lib/$(PROCESSOR)-linux-gnu
else
# Attempt to determine library location from gcc configuration.
PLATFORM_LIBDIR=$(shell gcc -v 2>&1 | grep "Configured with:" | sed 's/.*--libdir=\(\/usr\/[^ ]*\).*/\1/g')
endif
# Override for explicitly specified ARCHFLAG.
# Use locally compiled libcaprights in this case, on the
# assumption that any installed version is 64-bit.
ifeq ($(ARCHFLAG),-m32)
PROCESSOR=i386
PLATFORM_LIBDIR=/usr/lib32
LIBCAPRIGHTS=./libcaprights.a
endif
ifeq ($(ARCHFLAG),-mx32)
PROCESSOR=x32
PLATFORM_LIBDIR=/usr/libx32
LIBCAPRIGHTS=./libcaprights.a
endif
# Detect presence of libsctp in normal Debian location
ifneq ($(wildcard $(PLATFORM_LIBDIR)/libsctp.a),)
LIBSCTP=-lsctp
CXXFLAGS=-DHAVE_SCTP
endif
ifneq ($(LIBCAPRIGHTS),)
# Build local libcaprights.a (assuming ./configure
# has already been done in libcaprights/)
LOCAL_LIBS=$(LIBCAPRIGHTS)
LIBCAPRIGHTS_OBJS=libcaprights/capsicum.o libcaprights/linux-bpf-capmode.o libcaprights/procdesc.o libcaprights/signal.o
LOCAL_CLEAN=$(LOCAL_LIBS) $(LIBCAPRIGHTS_OBJS)
else
# Detect installed libcaprights static library.
ifneq ($(wildcard $(PLATFORM_LIBDIR)/libcaprights.a),)
LIBCAPRIGHTS=$(PLATFORM_LIBDIR)/libcaprights.a
else
ifneq ($(wildcard /usr/lib/libcaprights.a),)
LIBCAPRIGHTS=/usr/lib/libcaprights.a
endif
endif
endif
endif
# Extra test programs for arch-transition tests
EXTRA_PROGS = mini-me.32 mini-me.64
ifneq ($(wildcard /usr/include/gnu/stubs-x32.h),)
EXTRA_PROGS += mini-me.x32
endif
# Chain on to the master makefile
include makefile
./libcaprights.a: $(LIBCAPRIGHTS_OBJS)
ar cr $@ $^
# Small static programs of known architectures
# These may require additional packages to be installed; for example, for Debian:
# - libc6-dev-i386 provides 32-bit headers for a 64-bit system
# - libc6-dev-x32 provides headers for the x32 ABI.
mini-me.32: mini-me.c
$(CC) $(CFLAGS) -m32 -static -o $@ $<
mini-me.x32: mini-me.c
$(CC) $(CFLAGS) -mx32 -static -o $@ $<
mini-me.64: mini-me.c
$(CC) $(CFLAGS) -m64 -static -o $@ $<

View File

@ -0,0 +1,26 @@
Copyright (c) 2009-2011 Robert N. M. Watson
Copyright (c) 2011 Jonathan Anderson
Copyright (C) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
Copyright (c) 2013-2014 Google Inc.
All rights reserved.
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.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -0,0 +1,62 @@
# Capsicum User Space Tests
This directory holds unit tests for [Capsicum](http://www.cl.cam.ac.uk/research/security/capsicum/)
object-capabilities. The tests exercise the syscall interface to a Capsicum-enabled operating system,
currently either [FreeBSD >=10.x](http://www.freebsd.org) or a modified Linux kernel (the
[capsicum-linux](http://github.com/google/capsicum-linux) project).
The tests are written in C++98, and use the [Google Test](https://code.google.com/p/googletest/)
framework, with some additions to fork off particular tests (because a process that enters capability
mode cannot leave it again).
## Provenance
The original basis for these tests was:
- [unit tests](https://github.com/freebsd/freebsd/tree/master/tools/regression/security/cap_test)
written by Robert Watson and Jonathan Anderson for the original FreeBSD 9.x Capsicum implementation
- [unit tests](http://git.chromium.org/gitweb/?p=chromiumos/third_party/kernel-capsicum.git;a=tree;f=tools/testing/capsicum_tests;hb=refs/heads/capsicum) written by Meredydd Luff for the original Capsicum-Linux port.
These tests were coalesced and moved into an independent repository to enable
comparative testing across multiple OSes, and then substantially extended.
## OS Configuration
### Linux
The following kernel configuration options are needed to run the tests:
- `CONFIG_SECURITY_CAPSICUM`: enable the Capsicum framework
- `CONFIG_PROCDESC`: enable Capsicum process-descriptor functionality
- `CONFIG_DEBUG_FS`: enable debug filesystem
- `CONFIG_IP_SCTP`: enable SCTP support
### FreeBSD (>= 10.x)
The following kernel configuration options are needed so that all tests can run:
- `options P1003_1B_MQUEUE`: Enable POSIX message queues (or `kldload mqueuefs`)
## Other Dependencies
### Linux
The following additional development packages are needed to build the full test suite on Linux.
- `libcaprights`: See below
- `libcap-dev`: Provides headers for POSIX.1e capabilities.
- `libsctp1`: Provides SCTP library functions.
- `libsctp-dev`: Provides headers for SCTP library functions.
## Linux libcaprights
The Capsicum userspace library is held in the `libcaprights/` subdirectory. Ideally, this
library should be built (with `./configure; make` or `dpkg-buildpackage -uc -us`) and
installed (with `make install` or `dpkg -i libcaprights*.deb`) so that the tests will
use behave like a normal Capsicum-aware application.
However, if no installed copy of the library is found, the `GNUmakefile` will attempt
to use the local `libcaprights/*.c` source; this requires `./configure` to have been
performed in the `libcaprights` subdirectory. The local code is also used for
cross-compiled builds of the test suite (e.g. `make ARCH=32` or `make ARCH=x32`).

View File

@ -0,0 +1,188 @@
// Tests involving 2 capability file descriptors.
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
TEST(CapabilityPair, sendfile) {
int in_fd = open(TmpFile("cap_sendfile_in"), O_CREAT|O_RDWR, 0644);
EXPECT_OK(write(in_fd, "1234", 4));
// Output fd for sendfile must be a stream socket in FreeBSD.
int sock_fds[2];
EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds));
cap_rights_t r_rs;
cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
cap_rights_t r_ws;
cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
int cap_in_ro = dup(in_fd);
EXPECT_OK(cap_in_ro);
EXPECT_OK(cap_rights_limit(cap_in_ro, &r_rs));
int cap_in_wo = dup(in_fd);
EXPECT_OK(cap_in_wo);
EXPECT_OK(cap_rights_limit(cap_in_wo, &r_ws));
int cap_out_ro = dup(sock_fds[0]);
EXPECT_OK(cap_out_ro);
EXPECT_OK(cap_rights_limit(cap_out_ro, &r_rs));
int cap_out_wo = dup(sock_fds[0]);
EXPECT_OK(cap_out_wo);
EXPECT_OK(cap_rights_limit(cap_out_wo, &r_ws));
off_t offset = 0;
EXPECT_NOTCAPABLE(sendfile_(cap_out_ro, cap_in_ro, &offset, 4));
EXPECT_NOTCAPABLE(sendfile_(cap_out_wo, cap_in_wo, &offset, 4));
EXPECT_OK(sendfile_(cap_out_wo, cap_in_ro, &offset, 4));
close(cap_in_ro);
close(cap_in_wo);
close(cap_out_ro);
close(cap_out_wo);
close(in_fd);
close(sock_fds[0]);
close(sock_fds[1]);
unlink(TmpFile("cap_sendfile_in"));
}
#ifdef HAVE_TEE
TEST(CapabilityPair, tee) {
int pipe1_fds[2];
EXPECT_OK(pipe2(pipe1_fds, O_NONBLOCK));
int pipe2_fds[2];
EXPECT_OK(pipe2(pipe2_fds, O_NONBLOCK));
// Put some data into pipe1.
unsigned char buffer[4] = {1, 2, 3, 4};
EXPECT_OK(write(pipe1_fds[1], buffer, 4));
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
cap_rights_t r_wo;
cap_rights_init(&r_wo, CAP_WRITE);
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
// Various attempts to tee into pipe2.
int cap_in_wo = dup(pipe1_fds[0]);
EXPECT_OK(cap_in_wo);
EXPECT_OK(cap_rights_limit(cap_in_wo, &r_wo));
int cap_in_rw = dup(pipe1_fds[0]);
EXPECT_OK(cap_in_rw);
EXPECT_OK(cap_rights_limit(cap_in_rw, &r_rw));
int cap_out_ro = dup(pipe2_fds[1]);
EXPECT_OK(cap_out_ro);
EXPECT_OK(cap_rights_limit(cap_out_ro, &r_ro));
int cap_out_rw = dup(pipe2_fds[1]);
EXPECT_OK(cap_out_rw);
EXPECT_OK(cap_rights_limit(cap_out_rw, &r_rw));
EXPECT_NOTCAPABLE(tee(cap_in_wo, cap_out_rw, 4, SPLICE_F_NONBLOCK));
EXPECT_NOTCAPABLE(tee(cap_in_rw, cap_out_ro, 4, SPLICE_F_NONBLOCK));
EXPECT_OK(tee(cap_in_rw, cap_out_rw, 4, SPLICE_F_NONBLOCK));
close(cap_in_wo);
close(cap_in_rw);
close(cap_out_ro);
close(cap_out_rw);
close(pipe1_fds[0]);
close(pipe1_fds[1]);
close(pipe2_fds[0]);
close(pipe2_fds[1]);
}
#endif
#ifdef HAVE_SPLICE
TEST(CapabilityPair, splice) {
int pipe1_fds[2];
EXPECT_OK(pipe2(pipe1_fds, O_NONBLOCK));
int pipe2_fds[2];
EXPECT_OK(pipe2(pipe2_fds, O_NONBLOCK));
// Put some data into pipe1.
unsigned char buffer[4] = {1, 2, 3, 4};
EXPECT_OK(write(pipe1_fds[1], buffer, 4));
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
cap_rights_t r_wo;
cap_rights_init(&r_wo, CAP_WRITE);
cap_rights_t r_rs;
cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
cap_rights_t r_ws;
cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
// Various attempts to splice.
int cap_in_wo = dup(pipe1_fds[0]);
EXPECT_OK(cap_in_wo);
EXPECT_OK(cap_rights_limit(cap_in_wo, &r_wo));
int cap_in_ro = dup(pipe1_fds[0]);
EXPECT_OK(cap_in_ro);
EXPECT_OK(cap_rights_limit(cap_in_ro, &r_ro));
int cap_in_ro_seek = dup(pipe1_fds[0]);
EXPECT_OK(cap_in_ro_seek);
EXPECT_OK(cap_rights_limit(cap_in_ro_seek, &r_rs));
int cap_out_wo = dup(pipe2_fds[1]);
EXPECT_OK(cap_out_wo);
EXPECT_OK(cap_rights_limit(cap_out_wo, &r_wo));
int cap_out_ro = dup(pipe2_fds[1]);
EXPECT_OK(cap_out_ro);
EXPECT_OK(cap_rights_limit(cap_out_ro, &r_ro));
int cap_out_wo_seek = dup(pipe2_fds[1]);
EXPECT_OK(cap_out_wo_seek);
EXPECT_OK(cap_rights_limit(cap_out_wo_seek, &r_ws));
EXPECT_NOTCAPABLE(splice(cap_in_ro, NULL, cap_out_wo_seek, NULL, 4, SPLICE_F_NONBLOCK));
EXPECT_NOTCAPABLE(splice(cap_in_wo, NULL, cap_out_wo_seek, NULL, 4, SPLICE_F_NONBLOCK));
EXPECT_NOTCAPABLE(splice(cap_in_ro_seek, NULL, cap_out_ro, NULL, 4, SPLICE_F_NONBLOCK));
EXPECT_NOTCAPABLE(splice(cap_in_ro_seek, NULL, cap_out_wo, NULL, 4, SPLICE_F_NONBLOCK));
EXPECT_OK(splice(cap_in_ro_seek, NULL, cap_out_wo_seek, NULL, 4, SPLICE_F_NONBLOCK));
close(cap_in_wo);
close(cap_in_ro);
close(cap_in_ro_seek);
close(cap_out_wo);
close(cap_out_ro);
close(cap_out_wo_seek);
close(pipe1_fds[0]);
close(pipe1_fds[1]);
close(pipe2_fds[0]);
close(pipe2_fds[1]);
}
#endif
#ifdef HAVE_VMSPLICE
// Although it only involves a single file descriptor, test vmsplice(2) here too.
TEST(CapabilityPair, vmsplice) {
int pipe_fds[2];
EXPECT_OK(pipe2(pipe_fds, O_NONBLOCK));
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
int cap_ro = dup(pipe_fds[1]);
EXPECT_OK(cap_ro);
EXPECT_OK(cap_rights_limit(cap_ro, &r_ro));
int cap_rw = dup(pipe_fds[1]);
EXPECT_OK(cap_rw);
EXPECT_OK(cap_rights_limit(cap_rw, &r_rw));
unsigned char buffer[4] = {1, 2, 3, 4};
struct iovec iov;
memset(&iov, 0, sizeof(iov));
iov.iov_base = buffer;
iov.iov_len = sizeof(buffer);
EXPECT_NOTCAPABLE(vmsplice(cap_ro, &iov, 1, SPLICE_F_NONBLOCK));
EXPECT_OK(vmsplice(cap_rw, &iov, 1, SPLICE_F_NONBLOCK));
close(cap_ro);
close(cap_rw);
close(pipe_fds[0]);
close(pipe_fds[1]);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,654 @@
// Test routines to make sure a variety of system calls are or are not
// available in capability mode. The goal is not to see if they work, just
// whether or not they return the expected ECAPMODE.
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ptrace.h>
#include <dirent.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sched.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
// Test fixture that opens (and closes) a bunch of files.
class WithFiles : public ::testing::Test {
public:
WithFiles() :
fd_file_(open(TmpFile("cap_capmode"), O_RDWR|O_CREAT, 0644)),
fd_close_(open("/dev/null", O_RDWR)),
fd_dir_(open(tmpdir.c_str(), O_RDONLY)),
fd_socket_(socket(PF_INET, SOCK_DGRAM, 0)),
fd_tcp_socket_(socket(PF_INET, SOCK_STREAM, 0)) {
EXPECT_OK(fd_file_);
EXPECT_OK(fd_close_);
EXPECT_OK(fd_dir_);
EXPECT_OK(fd_socket_);
EXPECT_OK(fd_tcp_socket_);
}
~WithFiles() {
if (fd_tcp_socket_ >= 0) close(fd_tcp_socket_);
if (fd_socket_ >= 0) close(fd_socket_);
if (fd_dir_ >= 0) close(fd_dir_);
if (fd_close_ >= 0) close(fd_close_);
if (fd_file_ >= 0) close(fd_file_);
unlink(TmpFile("cap_capmode"));
}
protected:
int fd_file_;
int fd_close_;
int fd_dir_;
int fd_socket_;
int fd_tcp_socket_;
};
FORK_TEST_F(WithFiles, DisallowedFileSyscalls) {
unsigned int mode = -1;
EXPECT_OK(cap_getmode(&mode));
EXPECT_EQ(0, (int)mode);
EXPECT_OK(cap_enter()); // Enter capability mode.
EXPECT_OK(cap_getmode(&mode));
EXPECT_EQ(1, (int)mode);
// System calls that are not permitted in capability mode.
EXPECT_CAPMODE(access(TmpFile("cap_capmode_access"), F_OK));
EXPECT_CAPMODE(acct(TmpFile("cap_capmode_acct")));
EXPECT_CAPMODE(chdir(TmpFile("cap_capmode_chdir")));
#ifdef HAVE_CHFLAGS
EXPECT_CAPMODE(chflags(TmpFile("cap_capmode_chflags"), UF_NODUMP));
#endif
EXPECT_CAPMODE(chmod(TmpFile("cap_capmode_chmod"), 0644));
EXPECT_CAPMODE(chown(TmpFile("cap_capmode_chown"), -1, -1));
EXPECT_CAPMODE(chroot(TmpFile("cap_capmode_chroot")));
EXPECT_CAPMODE(creat(TmpFile("cap_capmode_creat"), 0644));
EXPECT_CAPMODE(fchdir(fd_dir_));
#ifdef HAVE_GETFSSTAT
struct statfs statfs;
EXPECT_CAPMODE(getfsstat(&statfs, sizeof(statfs), MNT_NOWAIT));
#endif
EXPECT_CAPMODE(link(TmpFile("foo"), TmpFile("bar")));
struct stat sb;
EXPECT_CAPMODE(lstat(TmpFile("cap_capmode_lstat"), &sb));
EXPECT_CAPMODE(mknod(TmpFile("capmode_mknod"), 0644 | S_IFIFO, 0));
EXPECT_CAPMODE(bogus_mount_());
EXPECT_CAPMODE(open("/dev/null", O_RDWR));
char buf[64];
EXPECT_CAPMODE(readlink(TmpFile("cap_capmode_readlink"), buf, sizeof(buf)));
#ifdef HAVE_REVOKE
EXPECT_CAPMODE(revoke(TmpFile("cap_capmode_revoke")));
#endif
EXPECT_CAPMODE(stat(TmpFile("cap_capmode_stat"), &sb));
EXPECT_CAPMODE(symlink(TmpFile("cap_capmode_symlink_from"), TmpFile("cap_capmode_symlink_to")));
EXPECT_CAPMODE(unlink(TmpFile("cap_capmode_unlink")));
EXPECT_CAPMODE(umount2("/not_mounted", 0));
}
FORK_TEST_F(WithFiles, DisallowedSocketSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode.
// System calls that are not permitted in capability mode.
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
EXPECT_CAPMODE(bind_(fd_socket_, (sockaddr*)&addr, sizeof(addr)));
addr.sin_family = AF_INET;
addr.sin_port = 53;
addr.sin_addr.s_addr = htonl(0x08080808);
EXPECT_CAPMODE(connect_(fd_tcp_socket_, (sockaddr*)&addr, sizeof(addr)));
}
FORK_TEST_F(WithFiles, AllowedFileSyscalls) {
int rc;
EXPECT_OK(cap_enter()); // Enter capability mode.
EXPECT_OK(close(fd_close_));
fd_close_ = -1;
int fd_dup = dup(fd_file_);
EXPECT_OK(fd_dup);
EXPECT_OK(dup2(fd_file_, fd_dup));
#ifdef HAVE_DUP3
EXPECT_OK(dup3(fd_file_, fd_dup, 0));
#endif
if (fd_dup >= 0) close(fd_dup);
struct stat sb;
EXPECT_OK(fstat(fd_file_, &sb));
EXPECT_OK(lseek(fd_file_, 0, SEEK_SET));
char ch;
EXPECT_OK(read(fd_file_, &ch, sizeof(ch)));
EXPECT_OK(write(fd_file_, &ch, sizeof(ch)));
#ifdef HAVE_CHFLAGS
rc = fchflags(fd_file_, UF_NODUMP);
if (rc < 0) {
EXPECT_NE(ECAPMODE, errno);
}
#endif
char buf[1024];
rc = getdents_(fd_dir_, (void*)buf, sizeof(buf));
EXPECT_OK(rc);
char data[] = "123";
EXPECT_OK(pwrite(fd_file_, data, 1, 0));
EXPECT_OK(pread(fd_file_, data, 1, 0));
struct iovec io;
io.iov_base = data;
io.iov_len = 2;
#if !defined(__i386__) && !defined(__linux__)
// TODO(drysdale): reinstate these tests for 32-bit runs when possible
// libc bug is fixed.
EXPECT_OK(pwritev(fd_file_, &io, 1, 0));
EXPECT_OK(preadv(fd_file_, &io, 1, 0));
#endif
EXPECT_OK(writev(fd_file_, &io, 1));
EXPECT_OK(readv(fd_file_, &io, 1));
#ifdef HAVE_SYNCFS
EXPECT_OK(syncfs(fd_file_));
#endif
#ifdef HAVE_SYNC_FILE_RANGE
EXPECT_OK(sync_file_range(fd_file_, 0, 1, 0));
#endif
#ifdef HAVE_READAHEAD
if (!tmpdir_on_tmpfs) { // tmpfs doesn't support readahead(2)
EXPECT_OK(readahead(fd_file_, 0, 1));
}
#endif
}
FORK_TEST_F(WithFiles, AllowedSocketSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode.
// recvfrom() either returns -1 with EAGAIN, or 0.
int rc = recvfrom(fd_socket_, NULL, 0, MSG_DONTWAIT, NULL, NULL);
if (rc < 0) {
EXPECT_EQ(EAGAIN, errno);
}
char ch;
EXPECT_OK(write(fd_file_, &ch, sizeof(ch)));
// These calls will fail for lack of e.g. a proper name to send to,
// but they are allowed in capability mode, so errno != ECAPMODE.
EXPECT_FAIL_NOT_CAPMODE(accept(fd_socket_, NULL, NULL));
EXPECT_FAIL_NOT_CAPMODE(getpeername(fd_socket_, NULL, NULL));
EXPECT_FAIL_NOT_CAPMODE(getsockname(fd_socket_, NULL, NULL));
EXPECT_FAIL_NOT_CAPMODE(recvmsg(fd_socket_, NULL, 0));
EXPECT_FAIL_NOT_CAPMODE(sendmsg(fd_socket_, NULL, 0));
EXPECT_FAIL_NOT_CAPMODE(sendto(fd_socket_, NULL, 0, 0, NULL, 0));
off_t offset = 0;
EXPECT_FAIL_NOT_CAPMODE(sendfile_(fd_socket_, fd_file_, &offset, 1));
// The socket/socketpair syscalls are allowed, but they don't give
// anything externally useful (can't call bind/connect on them).
int fd_socket2 = socket(PF_INET, SOCK_DGRAM, 0);
EXPECT_OK(fd_socket2);
if (fd_socket2 >= 0) close(fd_socket2);
int fd_pair[2] = {-1, -1};
EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, fd_pair));
if (fd_pair[0] >= 0) close(fd_pair[0]);
if (fd_pair[1] >= 0) close(fd_pair[1]);
}
#ifdef HAVE_SEND_RECV_MMSG
FORK_TEST(Capmode, AllowedMmsgSendRecv) {
int fd_socket = socket(PF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
EXPECT_OK(bind(fd_socket, (sockaddr*)&addr, sizeof(addr)));
EXPECT_OK(cap_enter()); // Enter capability mode.
char buffer[256] = {0};
struct iovec iov;
iov.iov_base = buffer;
iov.iov_len = sizeof(buffer);
struct mmsghdr mm;
memset(&mm, 0, sizeof(mm));
mm.msg_hdr.msg_iov = &iov;
mm.msg_hdr.msg_iovlen = 1;
struct timespec ts;
ts.tv_sec = 1;
ts.tv_nsec = 100;
EXPECT_FAIL_NOT_CAPMODE(recvmmsg(fd_socket, &mm, 1, MSG_DONTWAIT, &ts));
EXPECT_FAIL_NOT_CAPMODE(sendmmsg(fd_socket, &mm, 1, 0));
close(fd_socket);
}
#endif
FORK_TEST(Capmode, AllowedIdentifierSyscalls) {
// Record some identifiers
gid_t my_gid = getgid();
pid_t my_pid = getpid();
pid_t my_ppid = getppid();
uid_t my_uid = getuid();
pid_t my_sid = getsid(my_pid);
EXPECT_OK(cap_enter()); // Enter capability mode.
EXPECT_EQ(my_gid, getegid_());
EXPECT_EQ(my_uid, geteuid_());
EXPECT_EQ(my_gid, getgid_());
EXPECT_EQ(my_pid, getpid());
EXPECT_EQ(my_ppid, getppid());
EXPECT_EQ(my_uid, getuid_());
EXPECT_EQ(my_sid, getsid(my_pid));
gid_t grps[128];
EXPECT_OK(getgroups_(128, grps));
uid_t ruid;
uid_t euid;
uid_t suid;
EXPECT_OK(getresuid(&ruid, &euid, &suid));
gid_t rgid;
gid_t egid;
gid_t sgid;
EXPECT_OK(getresgid(&rgid, &egid, &sgid));
#ifdef HAVE_GETLOGIN
EXPECT_TRUE(getlogin() != NULL);
#endif
// Set various identifiers (to their existing values).
EXPECT_OK(setgid(my_gid));
#ifdef HAVE_SETFSGID
EXPECT_OK(setfsgid(my_gid));
#endif
EXPECT_OK(setuid(my_uid));
#ifdef HAVE_SETFSUID
EXPECT_OK(setfsuid(my_uid));
#endif
EXPECT_OK(setregid(my_gid, my_gid));
EXPECT_OK(setresgid(my_gid, my_gid, my_gid));
EXPECT_OK(setreuid(my_uid, my_uid));
EXPECT_OK(setresuid(my_uid, my_uid, my_uid));
EXPECT_OK(setsid());
}
FORK_TEST(Capmode, AllowedSchedSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode.
int policy = sched_getscheduler(0);
EXPECT_OK(policy);
struct sched_param sp;
EXPECT_OK(sched_getparam(0, &sp));
if (policy >= 0 && (!SCHED_SETSCHEDULER_REQUIRES_ROOT || getuid() == 0)) {
EXPECT_OK(sched_setscheduler(0, policy, &sp));
}
EXPECT_OK(sched_setparam(0, &sp));
EXPECT_OK(sched_get_priority_max(policy));
EXPECT_OK(sched_get_priority_min(policy));
struct timespec ts;
EXPECT_OK(sched_rr_get_interval(0, &ts));
EXPECT_OK(sched_yield());
}
FORK_TEST(Capmode, AllowedTimerSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode.
struct timespec ts;
EXPECT_OK(clock_getres(CLOCK_REALTIME, &ts));
EXPECT_OK(clock_gettime(CLOCK_REALTIME, &ts));
struct itimerval itv;
EXPECT_OK(getitimer(ITIMER_REAL, &itv));
EXPECT_OK(setitimer(ITIMER_REAL, &itv, NULL));
struct timeval tv;
struct timezone tz;
EXPECT_OK(gettimeofday(&tv, &tz));
ts.tv_sec = 0;
ts.tv_nsec = 1;
EXPECT_OK(nanosleep(&ts, NULL));
}
FORK_TEST(Capmode, AllowedProfilSyscall) {
EXPECT_OK(cap_enter()); // Enter capability mode.
char sbuf[32];
EXPECT_OK(profil((profil_arg1_t*)sbuf, sizeof(sbuf), 0, 1));
}
FORK_TEST(Capmode, AllowedResourceSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode.
errno = 0;
int rc = getpriority(PRIO_PROCESS, 0);
EXPECT_EQ(0, errno);
EXPECT_OK(setpriority(PRIO_PROCESS, 0, rc));
struct rlimit rlim;
EXPECT_OK(getrlimit_(RLIMIT_CORE, &rlim));
EXPECT_OK(setrlimit(RLIMIT_CORE, &rlim));
struct rusage ruse;
EXPECT_OK(getrusage(RUSAGE_SELF, &ruse));
}
FORK_TEST(CapMode, AllowedMmapSyscalls) {
// mmap() some memory.
size_t mem_size = getpagesize();
void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
EXPECT_TRUE(mem != NULL);
EXPECT_OK(cap_enter()); // Enter capability mode.
EXPECT_OK(msync(mem, mem_size, MS_ASYNC));
EXPECT_OK(madvise(mem, mem_size, MADV_NORMAL));
unsigned char vec[2];
EXPECT_OK(mincore_(mem, mem_size, vec));
EXPECT_OK(mprotect(mem, mem_size, PROT_READ|PROT_WRITE));
if (!MLOCK_REQUIRES_ROOT || getuid() == 0) {
EXPECT_OK(mlock(mem, mem_size));
EXPECT_OK(munlock(mem, mem_size));
int rc = mlockall(MCL_CURRENT);
if (rc != 0) {
// mlockall may well fail with ENOMEM for non-root users, as the
// default RLIMIT_MEMLOCK value isn't that big.
EXPECT_NE(ECAPMODE, errno);
}
EXPECT_OK(munlockall());
}
// Unmap the memory.
EXPECT_OK(munmap(mem, mem_size));
}
FORK_TEST(Capmode, AllowedPipeSyscalls) {
EXPECT_OK(cap_enter()); // Enter capability mode
int fd2[2];
int rc = pipe(fd2);
EXPECT_EQ(0, rc);
#ifdef HAVE_VMSPLICE
char buf[11] = "0123456789";
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
EXPECT_FAIL_NOT_CAPMODE(vmsplice(fd2[0], &iov, 1, SPLICE_F_NONBLOCK));
#endif
if (rc == 0) {
close(fd2[0]);
close(fd2[1]);
};
#ifdef HAVE_PIPE2
rc = pipe2(fd2, 0);
EXPECT_EQ(0, rc);
if (rc == 0) {
close(fd2[0]);
close(fd2[1]);
};
#endif
}
TEST(Capmode, AllowedAtSyscalls) {
int rc = mkdir(TmpFile("cap_at_syscalls"), 0755);
EXPECT_OK(rc);
if (rc < 0 && errno != EEXIST) return;
int dfd = open(TmpFile("cap_at_syscalls"), O_RDONLY);
EXPECT_OK(dfd);
int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644);
EXPECT_OK(file);
EXPECT_OK(close(file));
pid_t child = fork();
if (child == 0) {
// Child: enter cap mode and run tests
EXPECT_OK(cap_enter()); // Enter capability mode
struct stat fs;
EXPECT_OK(fstatat(dfd, "testfile", &fs, 0));
EXPECT_OK(mkdirat(dfd, "subdir", 0600));
EXPECT_OK(fchmodat(dfd, "subdir", 0644, 0));
EXPECT_OK(faccessat(dfd, "subdir", F_OK, 0));
EXPECT_OK(renameat(dfd, "subdir", dfd, "subdir2"));
EXPECT_OK(renameat(dfd, "subdir2", dfd, "subdir"));
struct timeval tv[2];
struct timezone tz;
EXPECT_OK(gettimeofday(&tv[0], &tz));
EXPECT_OK(gettimeofday(&tv[1], &tz));
EXPECT_OK(futimesat(dfd, "testfile", tv));
EXPECT_OK(fchownat(dfd, "testfile", fs.st_uid, fs.st_gid, 0));
EXPECT_OK(linkat(dfd, "testfile", dfd, "linky", 0));
EXPECT_OK(symlinkat("testfile", dfd, "symlink"));
char buffer[256];
EXPECT_OK(readlinkat(dfd, "symlink", buffer, sizeof(buffer)));
EXPECT_OK(unlinkat(dfd, "linky", 0));
EXPECT_OK(unlinkat(dfd, "subdir", AT_REMOVEDIR));
// Check that invalid requests get a non-Capsicum errno.
errno = 0;
rc = readlinkat(-1, "symlink", buffer, sizeof(buffer));
EXPECT_GE(0, rc);
EXPECT_NE(ECAPMODE, errno);
exit(HasFailure());
}
// Wait for the child.
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
// Tidy up.
close(dfd);
rmdir(TmpFile("cap_at_syscalls/subdir"));
unlink(TmpFile("cap_at_syscalls/symlink"));
unlink(TmpFile("cap_at_syscalls/linky"));
unlink(TmpFile("cap_at_syscalls/testfile"));
rmdir(TmpFile("cap_at_syscalls"));
}
TEST(Capmode, AllowedAtSyscallsCwd) {
int rc = mkdir(TmpFile("cap_at_syscalls_cwd"), 0755);
EXPECT_OK(rc);
if (rc < 0 && errno != EEXIST) return;
int dfd = open(TmpFile("cap_at_syscalls_cwd"), O_RDONLY);
EXPECT_OK(dfd);
int file = openat(dfd, "testfile", O_RDONLY|O_CREAT, 0644);
EXPECT_OK(file);
EXPECT_OK(close(file));
pid_t child = fork();
if (child == 0) {
// Child: move into temp dir, enter cap mode and run tests
EXPECT_OK(fchdir(dfd));
EXPECT_OK(cap_enter()); // Enter capability mode
// Test that *at(AT_FDCWD, path,...) is policed with ECAPMODE.
EXPECT_CAPMODE(openat(AT_FDCWD, "testfile", O_RDONLY));
struct stat fs;
EXPECT_CAPMODE(fstatat(AT_FDCWD, "testfile", &fs, 0));
EXPECT_CAPMODE(mkdirat(AT_FDCWD, "subdir", 0600));
EXPECT_CAPMODE(fchmodat(AT_FDCWD, "subdir", 0644, 0));
EXPECT_CAPMODE(faccessat(AT_FDCWD, "subdir", F_OK, 0));
EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir", AT_FDCWD, "subdir2"));
EXPECT_CAPMODE(renameat(AT_FDCWD, "subdir2", AT_FDCWD, "subdir"));
struct timeval tv[2];
struct timezone tz;
EXPECT_OK(gettimeofday(&tv[0], &tz));
EXPECT_OK(gettimeofday(&tv[1], &tz));
EXPECT_CAPMODE(futimesat(AT_FDCWD, "testfile", tv));
EXPECT_CAPMODE(fchownat(AT_FDCWD, "testfile", fs.st_uid, fs.st_gid, 0));
EXPECT_CAPMODE(linkat(AT_FDCWD, "testfile", AT_FDCWD, "linky", 0));
EXPECT_CAPMODE(symlinkat("testfile", AT_FDCWD, "symlink"));
char buffer[256];
EXPECT_CAPMODE(readlinkat(AT_FDCWD, "symlink", buffer, sizeof(buffer)));
EXPECT_CAPMODE(unlinkat(AT_FDCWD, "linky", 0));
exit(HasFailure());
}
// Wait for the child.
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
// Tidy up.
close(dfd);
rmdir(TmpFile("cap_at_syscalls_cwd/subdir"));
unlink(TmpFile("cap_at_syscalls_cwd/symlink"));
unlink(TmpFile("cap_at_syscalls_cwd/linky"));
unlink(TmpFile("cap_at_syscalls_cwd/testfile"));
rmdir(TmpFile("cap_at_syscalls_cwd"));
}
TEST(Capmode, Abort) {
// Check that abort(3) works even in capability mode.
pid_t child = fork();
if (child == 0) {
// Child: enter capability mode and call abort(3).
// Triggers something like kill(getpid(), SIGABRT).
cap_enter(); // Enter capability mode.
abort();
exit(99);
}
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
EXPECT_TRUE(WIFSIGNALED(status)) << " status = " << std::hex << status;
EXPECT_EQ(SIGABRT, WTERMSIG(status)) << " status = " << std::hex << status;
}
FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
umask(022);
mode_t um_before = umask(022);
EXPECT_OK(cap_enter()); // Enter capability mode.
mode_t um = umask(022);
EXPECT_NE(-ECAPMODE, (int)um);
EXPECT_EQ(um_before, um);
stack_t ss;
EXPECT_OK(sigaltstack(NULL, &ss));
// Finally, tests for system calls that don't fit the pattern very well.
pid_t pid = fork();
EXPECT_OK(pid);
if (pid == 0) {
// Child: almost immediately exit.
sleep(1);
exit(0);
} else if (pid > 0) {
errno = 0;
EXPECT_CAPMODE(ptrace_(PTRACE_PEEKDATA_, pid, &pid, NULL));
EXPECT_CAPMODE(waitpid(pid, NULL, 0));
}
// No error return from sync(2) to test, but check errno remains unset.
errno = 0;
sync();
EXPECT_EQ(0, errno);
// TODO(FreeBSD): ktrace
#ifdef HAVE_SYSARCH
// sysarch() is, by definition, architecture-dependent
#if defined (__amd64__) || defined (__i386__)
long sysarch_arg = 0;
EXPECT_CAPMODE(sysarch(I386_SET_IOPERM, &sysarch_arg));
#else
// TOOD(jra): write a test for other architectures, like arm
#endif
#endif
}
void *thread_fn(void *p) {
int delay = *(int *)p;
sleep(delay);
EXPECT_OK(getpid_());
EXPECT_CAPMODE(open("/dev/null", O_RDWR));
return NULL;
}
// Check that restrictions are the same in subprocesses and threads
FORK_TEST(Capmode, NewThread) {
// Fire off a new thread before entering capability mode
pthread_t early_thread;
int one = 1; // second
EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, &one));
// Fire off a new process before entering capability mode.
int early_child = fork();
EXPECT_OK(early_child);
if (early_child == 0) {
// Child: wait and then confirm this process is unaffect by capability mode in the parent.
sleep(1);
int fd = open("/dev/null", O_RDWR);
EXPECT_OK(fd);
close(fd);
exit(0);
}
EXPECT_OK(cap_enter()); // Enter capability mode.
// Do an allowed syscall.
EXPECT_OK(getpid_());
int child = fork();
EXPECT_OK(child);
if (child == 0) {
// Child: do an allowed and a disallowed syscall.
EXPECT_OK(getpid_());
EXPECT_CAPMODE(open("/dev/null", O_RDWR));
exit(0);
}
// Don't (can't) wait for either child.
// Wait for the early-started thread.
EXPECT_OK(pthread_join(early_thread, NULL));
// Fire off a new thread.
pthread_t child_thread;
int zero = 0; // seconds
EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, &zero));
EXPECT_OK(pthread_join(child_thread, NULL));
// Fork a subprocess which fires off a new thread.
child = fork();
EXPECT_OK(child);
if (child == 0) {
pthread_t child_thread2;
EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, &zero));
EXPECT_OK(pthread_join(child_thread2, NULL));
exit(0);
}
// Sleep for a bit to allow the subprocess to finish.
sleep(2);
}
static int had_signal = 0;
static void handle_signal(int) { had_signal = 1; }
FORK_TEST(Capmode, SelfKill) {
pid_t me = getpid();
sighandler_t original = signal(SIGUSR1, handle_signal);
pid_t child = fork();
if (child == 0) {
// Child: sleep and exit
sleep(1);
exit(0);
}
EXPECT_OK(cap_enter()); // Enter capability mode.
// Can only kill(2) to own pid.
EXPECT_CAPMODE(kill(child, SIGUSR1));
EXPECT_OK(kill(me, SIGUSR1));
EXPECT_EQ(1, had_signal);
signal(SIGUSR1, original);
}

View File

@ -0,0 +1,73 @@
#ifndef __CAPSICUM_FREEBSD_H__
#define __CAPSICUM_FREEBSD_H__
#ifdef __FreeBSD__
/************************************************************
* FreeBSD Capsicum Functionality.
************************************************************/
#ifdef __cplusplus
extern "C" {
#endif
/* FreeBSD definitions. */
#include <errno.h>
#include <sys/param.h>
#if __FreeBSD_version >= 1100014 || \
(__FreeBSD_version >= 1001511 && __FreeBSD_version < 1100000)
#include <sys/capsicum.h>
#else
#include <sys/capability.h>
#endif
#include <sys/procdesc.h>
#if __FreeBSD_version >= 1000000
#define AT_SYSCALLS_IN_CAPMODE
#define HAVE_CAP_RIGHTS_GET
#define HAVE_CAP_RIGHTS_LIMIT
#define HAVE_PROCDESC_FSTAT
#define HAVE_CAP_FCNTLS_LIMIT
// fcntl(2) takes int, cap_fcntls_limit(2) takes uint32_t.
typedef uint32_t cap_fcntl_t;
#define HAVE_CAP_IOCTLS_LIMIT
// ioctl(2) and cap_ioctls_limit(2) take unsigned long.
typedef unsigned long cap_ioctl_t;
#if __FreeBSD_version >= 1101000
#define HAVE_OPENAT_INTERMEDIATE_DOTDOT
#endif
#endif
#ifdef __cplusplus
}
#endif
// Use fexecve_() in tests to allow Linux variant to bypass glibc version.
#define fexecve_(F, A, E) fexecve(F, A, E)
#ifdef ENOTBENEATH
#define E_NO_TRAVERSE_CAPABILITY ENOTBENEATH
#define E_NO_TRAVERSE_O_BENEATH ENOTBENEATH
#else
#define E_NO_TRAVERSE_CAPABILITY ENOTCAPABLE
#define E_NO_TRAVERSE_O_BENEATH ENOTCAPABLE
#endif
// FreeBSD limits the number of ioctls in cap_ioctls_limit to 256
#define CAP_IOCTLS_LIMIT_MAX 256
// Too many links
#define E_TOO_MANY_LINKS EMLINK
// TODO(FreeBSD): uncomment if/when FreeBSD propagates rights on accept.
// FreeBSD does not generate a capability from accept(cap_fd,...).
// https://bugs.freebsd.org/201052
// #define CAP_FROM_ACCEPT
// TODO(FreeBSD): uncomment if/when FreeBSD propagates rights on sctp_peeloff.
// FreeBSD does not generate a capability from sctp_peeloff(cap_fd,...).
// https://bugs.freebsd.org/201052
// #define CAP_FROM_PEELOFF
#endif /* __FreeBSD__ */
#endif /*__CAPSICUM_FREEBSD_H__*/

View File

@ -0,0 +1,40 @@
#ifndef __CAPSICUM_LINUX_H__
#define __CAPSICUM_LINUX_H__
#ifdef __linux__
/************************************************************
* Linux Capsicum Functionality.
************************************************************/
#include <errno.h>
#include <sys/procdesc.h>
#include <sys/capsicum.h>
#define HAVE_CAP_RIGHTS_LIMIT
#define HAVE_CAP_RIGHTS_GET
#define HAVE_CAP_FCNTLS_LIMIT
#define HAVE_CAP_IOCTLS_LIMIT
#define HAVE_PROC_FDINFO
#define HAVE_PDWAIT4
#define CAP_FROM_ACCEPT
// TODO(drysdale): uncomment if/when Linux propagates rights on sctp_peeloff.
// Linux does not generate a capability from sctp_peeloff(cap_fd,...).
// #define CAP_FROM_PEELOFF
// TODO(drysdale): uncomment if/when Linux allows intermediate .. path segments
// for openat()-like operations.
// #define HAVE_OPENAT_INTERMEDIATE_DOTDOT
// Failure to open file due to path traversal generates EPERM
#ifdef ENOTBENEATH
#define E_NO_TRAVERSE_CAPABILITY ENOTBENEATH
#define E_NO_TRAVERSE_O_BENEATH ENOTBENEATH
#else
#define E_NO_TRAVERSE_CAPABILITY EPERM
#define E_NO_TRAVERSE_O_BENEATH EPERM
#endif
// Too many links
#define E_TOO_MANY_LINKS ELOOP
#endif /* __linux__ */
#endif /*__CAPSICUM_LINUX_H__*/

View File

@ -0,0 +1,118 @@
#ifndef __CAPSICUM_RIGHTS_H__
#define __CAPSICUM_RIGHTS_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __FreeBSD__
#include <sys/param.h>
#if __FreeBSD_version >= 1100014 || \
(__FreeBSD_version >= 1001511 && __FreeBSD_version < 1100000)
#include <sys/capsicum.h>
#else
#include <sys/capability.h>
#endif
#endif
#ifdef __linux__
#include <linux/capsicum.h>
#endif
#ifdef __cplusplus
}
#endif
#ifndef CAP_RIGHTS_VERSION
/************************************************************
* Capsicum compatibility layer: implement new (FreeBSD10.x)
* rights manipulation API in terms of original (FreeBSD9.x)
* functionality.
************************************************************/
#include <stdarg.h>
#include <stdbool.h>
/* Rights manipulation macros/functions.
* Note that these use variadic macros, available in C99 / C++11 (and
* also in earlier gcc versions).
*/
#define cap_rights_init(rights, ...) _cap_rights_init((rights), __VA_ARGS__, 0ULL)
#define cap_rights_set(rights, ...) _cap_rights_set((rights), __VA_ARGS__, 0ULL)
#define cap_rights_clear(rights, ...) _cap_rights_clear((rights), __VA_ARGS__, 0ULL)
#define cap_rights_is_set(rights, ...) _cap_rights_is_set((rights), __VA_ARGS__, 0ULL)
inline cap_rights_t* _cap_rights_init(cap_rights_t *rights, ...) {
va_list ap;
cap_rights_t right;
*rights = 0;
va_start(ap, rights);
while (true) {
right = va_arg(ap, cap_rights_t);
*rights |= right;
if (right == 0) break;
}
va_end(ap);
return rights;
}
inline cap_rights_t* _cap_rights_set(cap_rights_t *rights, ...) {
va_list ap;
cap_rights_t right;
va_start(ap, rights);
while (true) {
right = va_arg(ap, cap_rights_t);
*rights |= right;
if (right == 0) break;
}
va_end(ap);
return rights;
}
inline cap_rights_t* _cap_rights_clear(cap_rights_t *rights, ...) {
va_list ap;
cap_rights_t right;
va_start(ap, rights);
while (true) {
right = va_arg(ap, cap_rights_t);
*rights &= ~right;
if (right == 0) break;
}
va_end(ap);
return rights;
}
inline bool _cap_rights_is_set(const cap_rights_t *rights, ...) {
va_list ap;
cap_rights_t right;
cap_rights_t accumulated = 0;
va_start(ap, rights);
while (true) {
right = va_arg(ap, cap_rights_t);
accumulated |= right;
if (right == 0) break;
}
va_end(ap);
return (accumulated & *rights) == accumulated;
}
inline bool _cap_rights_is_valid(const cap_rights_t *rights) {
return true;
}
inline cap_rights_t* cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src) {
*dst |= *src;
return dst;
}
inline cap_rights_t* cap_rights_remove(cap_rights_t *dst, const cap_rights_t *src) {
*dst &= ~(*src);
return dst;
}
inline bool cap_rights_contains(const cap_rights_t *big, const cap_rights_t *little) {
return ((*big) & (*little)) == (*little);
}
#endif /* old/new style rights manipulation */
#endif /*__CAPSICUM_RIGHTS_H__*/

View File

@ -0,0 +1,156 @@
#include <sys/types.h>
#ifdef __linux__
#include <sys/vfs.h>
#include <linux/magic.h>
#elif defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include "gtest/gtest.h"
#include "capsicum-test.h"
// For versions of googletest that lack GTEST_SKIP.
#ifndef GTEST_SKIP
#define GTEST_SKIP GTEST_FAIL
#endif
std::string tmpdir;
class SetupEnvironment : public ::testing::Environment
{
public:
SetupEnvironment() : teardown_tmpdir_(false) {}
void SetUp() override {
CheckCapsicumSupport();
if (tmpdir.empty()) {
std::cerr << "Generating temporary directory root: ";
CreateTemporaryRoot();
} else {
std::cerr << "User provided temporary directory root: ";
}
std::cerr << tmpdir << std::endl;
}
void CheckCapsicumSupport() {
#ifdef __FreeBSD__
int rc;
bool trap_enotcap_enabled;
size_t trap_enotcap_enabled_len = sizeof(trap_enotcap_enabled);
if (feature_present("security_capabilities") == 0) {
GTEST_SKIP() << "Skipping tests because capsicum support is not "
<< "enabled in the kernel.";
}
// If this OID is enabled, it will send SIGTRAP to the process when
// `ENOTCAPABLE` is returned.
const char *oid = "kern.trap_enotcap";
rc = sysctlbyname(oid, &trap_enotcap_enabled, &trap_enotcap_enabled_len,
nullptr, 0);
if (rc != 0) {
GTEST_FAIL() << "sysctlbyname failed: " << strerror(errno);
}
if (trap_enotcap_enabled) {
GTEST_SKIP() << "Debug sysctl, " << oid << ", enabled. "
<< "Skipping tests because its enablement invalidates the "
<< "test results.";
}
#endif /* FreeBSD */
}
void CreateTemporaryRoot() {
char *tmpdir_name = tempnam(nullptr, "cptst");
ASSERT_NE(tmpdir_name, nullptr);
ASSERT_EQ(mkdir(tmpdir_name, 0700), 0) <<
"Could not create temp directory, " << tmpdir_name << ": " <<
strerror(errno);
tmpdir = std::string(tmpdir_name);
free(tmpdir_name);
teardown_tmpdir_ = true;
}
void TearDown() override {
if (teardown_tmpdir_) {
rmdir(tmpdir.c_str());
}
}
private:
bool teardown_tmpdir_;
};
std::string capsicum_test_bindir;
int main(int argc, char* argv[]) {
// Set up the test program path, so capsicum-test can find programs, like
// mini-me* when executed from an absolute path.
{
char *new_path, *old_path, *program_name;
program_name = strdup(argv[0]);
assert(program_name);
capsicum_test_bindir = std::string(dirname(program_name));
free(program_name);
old_path = getenv("PATH");
assert(old_path);
assert(asprintf(&new_path, "%s:%s", capsicum_test_bindir.c_str(),
old_path) > 0);
assert(setenv("PATH", new_path, 1) == 0);
}
::testing::InitGoogleTest(&argc, argv);
for (int ii = 1; ii < argc; ii++) {
if (strcmp(argv[ii], "-v") == 0) {
verbose = true;
} else if (strcmp(argv[ii], "-T") == 0) {
ii++;
assert(ii < argc);
tmpdir = argv[ii];
struct stat info;
stat(tmpdir.c_str(), &info);
assert(S_ISDIR(info.st_mode));
} else if (strcmp(argv[ii], "-t") == 0) {
force_mt = true;
} else if (strcmp(argv[ii], "-F") == 0) {
force_nofork = true;
} else if (strcmp(argv[ii], "-u") == 0) {
if (++ii >= argc) {
std::cerr << "-u needs argument" << std::endl;
exit(1);
}
if (isdigit(argv[ii][0])) {
other_uid = atoi(argv[ii]);
} else {
struct passwd *p = getpwnam(argv[ii]);
if (!p) {
std::cerr << "Failed to get entry for " << argv[ii] << ", errno=" << errno << std::endl;
exit(1);
}
other_uid = p->pw_uid;
}
}
}
if (other_uid == 0) {
struct stat info;
if (stat(argv[0], &info) == 0) {
other_uid = info.st_uid;
}
}
#ifdef __linux__
// Check whether our temporary directory is on a tmpfs volume.
struct statfs fsinfo;
statfs(tmpdir.c_str(), &fsinfo);
tmpdir_on_tmpfs = (fsinfo.f_type == TMPFS_MAGIC);
#endif
testing::AddGlobalTestEnvironment(new SetupEnvironment());
int rc = RUN_ALL_TESTS();
ShowSkippedTests(std::cerr);
return rc;
}

View File

@ -0,0 +1,102 @@
#include "capsicum-test.h"
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <map>
#include <vector>
#include <string>
bool verbose = false;
bool tmpdir_on_tmpfs = false;
bool force_mt = false;
bool force_nofork = false;
uid_t other_uid = 0;
namespace {
std::map<std::string, std::string> tmp_paths;
}
const char *TmpFile(const char *p) {
std::string pathname(p);
if (tmp_paths.find(pathname) == tmp_paths.end()) {
std::string fullname = tmpdir + "/" + pathname;
tmp_paths[pathname] = fullname;
}
return tmp_paths[pathname].c_str();
}
char ProcessState(int pid) {
#ifdef __linux__
// Open the process status file.
char s[1024];
snprintf(s, sizeof(s), "/proc/%d/status", pid);
FILE *f = fopen(s, "r");
if (f == NULL) return '\0';
// Read the file line by line looking for the state line.
const char *prompt = "State:\t";
while (!feof(f)) {
fgets(s, sizeof(s), f);
if (!strncmp(s, prompt, strlen(prompt))) {
fclose(f);
return s[strlen(prompt)];
}
}
fclose(f);
return '?';
#endif
#ifdef __FreeBSD__
char buffer[1024];
snprintf(buffer, sizeof(buffer), "ps -p %d -o state | grep -v STAT", pid);
sig_t original = signal(SIGCHLD, SIG_IGN);
FILE* cmd = popen(buffer, "r");
usleep(50000); // allow any pending SIGCHLD signals to arrive
signal(SIGCHLD, original);
int result = fgetc(cmd);
fclose(cmd);
// Map FreeBSD codes to Linux codes.
switch (result) {
case EOF:
return '\0';
case 'D': // disk wait
case 'R': // runnable
case 'S': // sleeping
case 'T': // stopped
case 'Z': // zombie
return result;
case 'W': // idle interrupt thread
return 'S';
case 'I': // idle
return 'S';
case 'L': // waiting to acquire lock
default:
return '?';
}
#endif
}
typedef std::vector<std::string> TestList;
typedef std::map<std::string, TestList*> SkippedTestMap;
static SkippedTestMap skipped_tests;
void TestSkipped(const char *testcase, const char *test, const std::string& reason) {
if (skipped_tests.find(reason) == skipped_tests.end()) {
skipped_tests[reason] = new TestList;
}
std::string testname(testcase);
testname += ".";
testname += test;
skipped_tests[reason]->push_back(testname);
}
void ShowSkippedTests(std::ostream& os) {
for (SkippedTestMap::iterator skiplist = skipped_tests.begin();
skiplist != skipped_tests.end(); ++skiplist) {
os << "Following tests were skipped because: " << skiplist->first << std::endl;
for (size_t ii = 0; ii < skiplist->second->size(); ++ii) {
const std::string& testname((*skiplist->second)[ii]);
os << " " << testname << std::endl;
}
}
}

View File

@ -0,0 +1,260 @@
/* -*- C++ -*- */
#ifndef CAPSICUM_TEST_H
#define CAPSICUM_TEST_H
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <signal.h>
#include <ios>
#include <ostream>
#include "gtest/gtest.h"
extern bool verbose;
extern std::string tmpdir;
extern bool tmpdir_on_tmpfs;
extern bool force_mt;
extern bool force_nofork;
extern uid_t other_uid;
static inline void *WaitingThreadFn(void *) {
// Loop until cancelled
while (true) {
usleep(10000);
pthread_testcancel();
}
return NULL;
}
// If force_mt is set, run another thread in parallel with the test. This forces
// the kernel into multi-threaded mode.
template <typename T, typename Function>
void MaybeRunWithThread(T *self, Function fn) {
pthread_t subthread;
if (force_mt) {
pthread_create(&subthread, NULL, WaitingThreadFn, NULL);
}
(self->*fn)();
if (force_mt) {
pthread_cancel(subthread);
pthread_join(subthread, NULL);
}
}
template <typename Function>
void MaybeRunWithThread(Function fn) {
pthread_t subthread;
if (force_mt) {
pthread_create(&subthread, NULL, WaitingThreadFn, NULL);
}
(fn)();
if (force_mt) {
pthread_cancel(subthread);
pthread_join(subthread, NULL);
}
}
// Return the absolute path of a filename in the temp directory, `tmpdir`,
// with the given pathname, e.g., "/tmp/<pathname>", if `tmpdir` was set to
// "/tmp".
const char *TmpFile(const char *pathname);
// Run the given test function in a forked process, so that trapdoor
// entry doesn't affect other tests, and watch out for hung processes.
// Implemented as a macro to allow access to the test case instance's
// HasFailure() method, which is reported as the forked process's
// exit status.
#define _RUN_FORKED(INNERCODE, TESTCASENAME, TESTNAME) \
pid_t pid = force_nofork ? 0 : fork(); \
if (pid == 0) { \
INNERCODE; \
if (!force_nofork) { \
exit(HasFailure()); \
} \
} else if (pid > 0) { \
int rc, status; \
int remaining_us = 10000000; \
while (remaining_us > 0) { \
status = 0; \
rc = waitpid(pid, &status, WNOHANG); \
if (rc != 0) break; \
remaining_us -= 10000; \
usleep(10000); \
} \
if (remaining_us <= 0) { \
fprintf(stderr, "Warning: killing unresponsive test " \
"%s.%s (pid %d)\n", \
TESTCASENAME, TESTNAME, pid); \
kill(pid, SIGKILL); \
ADD_FAILURE() << "Test hung"; \
} else if (rc < 0) { \
fprintf(stderr, "Warning: waitpid error %s (%d)\n", \
strerror(errno), errno); \
ADD_FAILURE() << "Failed to wait for child"; \
} else { \
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; \
EXPECT_EQ(0, rc); \
} \
}
#define _RUN_FORKED_MEM(THIS, TESTFN, TESTCASENAME, TESTNAME) \
_RUN_FORKED(MaybeRunWithThread(THIS, &TESTFN), TESTCASENAME, TESTNAME);
#define _RUN_FORKED_FN(TESTFN, TESTCASENAME, TESTNAME) \
_RUN_FORKED(MaybeRunWithThread(&TESTFN), TESTCASENAME, TESTNAME);
// Run a test case in a forked process, possibly cleaning up a
// test file after completion
#define FORK_TEST_ON(test_case_name, test_name, test_file) \
static void test_case_name##_##test_name##_ForkTest(); \
TEST(test_case_name, test_name ## Forked) { \
_RUN_FORKED_FN(test_case_name##_##test_name##_ForkTest, \
#test_case_name, #test_name); \
const char *filename = test_file; \
if (filename) unlink(filename); \
} \
static void test_case_name##_##test_name##_ForkTest()
#define FORK_TEST(test_case_name, test_name) FORK_TEST_ON(test_case_name, test_name, NULL)
// Run a test case fixture in a forked process, so that trapdoors don't
// affect other tests.
#define ICLASS_NAME(test_case_name, test_name) Forked##test_case_name##_##test_name
#define FORK_TEST_F(test_case_name, test_name) \
class ICLASS_NAME(test_case_name, test_name) : public test_case_name { \
public: \
ICLASS_NAME(test_case_name, test_name)() {} \
void InnerTestBody(); \
}; \
TEST_F(ICLASS_NAME(test_case_name, test_name), _) { \
_RUN_FORKED_MEM(this, \
ICLASS_NAME(test_case_name, test_name)::InnerTestBody, \
#test_case_name, #test_name); \
} \
void ICLASS_NAME(test_case_name, test_name)::InnerTestBody()
// Emit errno information on failure
#define EXPECT_OK(v) EXPECT_LE(0, v) << " errno " << errno << " " << strerror(errno)
// Expect a syscall to fail with the given error.
#define EXPECT_SYSCALL_FAIL(E, C) \
do { \
EXPECT_GT(0, C); \
EXPECT_EQ(E, errno); \
} while (0)
// Expect a syscall to fail with anything other than the given error.
#define EXPECT_SYSCALL_FAIL_NOT(E, C) \
do { \
EXPECT_GT(0, C); \
EXPECT_NE(E, errno); \
} while (0)
// Expect a void syscall to fail with anything other than the given error.
#define EXPECT_VOID_SYSCALL_FAIL_NOT(E, C) \
do { \
errno = 0; \
C; \
EXPECT_NE(E, errno) << #C << " failed with ECAPMODE"; \
} while (0)
// Expect a system call to fail due to path traversal; exact error
// code is OS-specific.
#ifdef O_BENEATH
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
do { \
const int result = openat((fd), (path), (flags)); \
if (((flags) & O_BENEATH) == O_BENEATH) { \
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_O_BENEATH, result); \
} else { \
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \
} \
} while (0)
#else
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
do { \
const int result = openat((fd), (path), (flags)); \
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \
} while (0)
#endif
// Expect a system call to fail with ECAPMODE.
#define EXPECT_CAPMODE(C) EXPECT_SYSCALL_FAIL(ECAPMODE, C)
// Expect a system call to fail, but not with ECAPMODE.
#define EXPECT_FAIL_NOT_CAPMODE(C) EXPECT_SYSCALL_FAIL_NOT(ECAPMODE, C)
#define EXPECT_FAIL_VOID_NOT_CAPMODE(C) EXPECT_VOID_SYSCALL_FAIL_NOT(ECAPMODE, C)
// Expect a system call to fail with ENOTCAPABLE.
#define EXPECT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL(ENOTCAPABLE, C)
// Expect a system call to fail, but not with ENOTCAPABLE.
#define EXPECT_FAIL_NOT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL_NOT(ENOTCAPABLE, C)
// Expect a system call to fail with either ENOTCAPABLE or ECAPMODE.
#define EXPECT_CAPFAIL(C) \
do { \
int rc = C; \
EXPECT_GT(0, rc); \
EXPECT_TRUE(errno == ECAPMODE || errno == ENOTCAPABLE) \
<< #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno; \
} while (0)
// Ensure that 'rights' are a subset of 'max'.
#define EXPECT_RIGHTS_IN(rights, max) \
EXPECT_TRUE(cap_rights_contains((max), (rights))) \
<< "rights " << std::hex << *(rights) \
<< " not a subset of " << std::hex << *(max)
// Ensure rights are identical
#define EXPECT_RIGHTS_EQ(a, b) \
do { \
EXPECT_RIGHTS_IN((a), (b)); \
EXPECT_RIGHTS_IN((b), (a)); \
} while (0)
// Get the state of a process as a single character.
// - 'D': disk wait
// - 'R': runnable
// - 'S': sleeping/idle
// - 'T': stopped
// - 'Z': zombie
// On error, return either '?' or '\0'.
char ProcessState(int pid);
// Check process state reaches a particular expected state (or two).
// Retries a few times to allow for timing issues.
#define EXPECT_PID_REACHES_STATES(pid, expected1, expected2) { \
int counter = 5; \
char state; \
do { \
state = ProcessState(pid); \
if (state == expected1 || state == expected2) break; \
usleep(100000); \
} while (--counter > 0); \
EXPECT_TRUE(state == expected1 || state == expected2) \
<< " pid " << pid << " in state " << state; \
}
#define EXPECT_PID_ALIVE(pid) EXPECT_PID_REACHES_STATES(pid, 'R', 'S')
#define EXPECT_PID_DEAD(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', '\0')
#define EXPECT_PID_ZOMBIE(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', 'Z');
#define EXPECT_PID_GONE(pid) EXPECT_PID_REACHES_STATES(pid, '\0', '\0');
void ShowSkippedTests(std::ostream& os);
void TestSkipped(const char *testcase, const char *test, const std::string& reason);
#define TEST_SKIPPED(reason) \
do { \
const ::testing::TestInfo* const info = ::testing::UnitTest::GetInstance()->current_test_info(); \
std::cerr << "Skipping " << info->test_case_name() << "::" << info->name() << " because: " << reason << std::endl; \
TestSkipped(info->test_case_name(), info->name(), reason); \
} while (0)
// Mark a test that can only be run as root.
#define REQUIRE_ROOT() \
if (getuid() != 0) { \
TEST_SKIPPED("requires root"); \
return; \
}
#endif // CAPSICUM_TEST_H

View File

@ -0,0 +1,175 @@
/*
* Minimal portability layer for Capsicum-related features.
*/
#ifndef __CAPSICUM_H__
#define __CAPSICUM_H__
#ifdef __FreeBSD__
#include "capsicum-freebsd.h"
#endif
#ifdef __linux__
#include "capsicum-linux.h"
#endif
/*
* CAP_ALL/CAP_NONE is a value in FreeBSD9.x Capsicum, but a functional macro
* in FreeBSD10.x Capsicum. Always use CAP_SET_ALL/CAP_SET_NONE instead.
*/
#ifndef CAP_SET_ALL
#ifdef CAP_RIGHTS_VERSION
#define CAP_SET_ALL(rights) CAP_ALL(rights)
#else
#define CAP_SET_ALL(rights) *(rights) = CAP_MASK_VALID
#endif
#endif
#ifndef CAP_SET_NONE
#ifdef CAP_RIGHTS_VERSION
#define CAP_SET_NONE(rights) CAP_NONE(rights)
#else
#define CAP_SET_NONE(rights) *(rights) = 0
#endif
#endif
/************************************************************
* Define new-style rights in terms of old-style rights if
* absent.
************************************************************/
#include "capsicum-rights.h"
/*
* Cope with systems (e.g. FreeBSD 10.x) where CAP_RENAMEAT hasn't been split out.
* (src, dest): RENAMEAT, LINKAT => RENAMEAT_SOURCE, RENAMEAT_TARGET
*/
#ifndef CAP_RENAMEAT_SOURCE
#define CAP_RENAMEAT_SOURCE CAP_RENAMEAT
#endif
#ifndef CAP_RENAMEAT_TARGET
#define CAP_RENAMEAT_TARGET CAP_LINKAT
#endif
/*
* Cope with systems (e.g. FreeBSD 10.x) where CAP_RENAMEAT hasn't been split out.
* (src, dest): 0, LINKAT => LINKAT_SOURCE, LINKAT_TARGET
*/
#ifndef CAP_LINKAT_SOURCE
#define CAP_LINKAT_SOURCE CAP_LOOKUP
#endif
#ifndef CAP_LINKAT_TARGET
#define CAP_LINKAT_TARGET CAP_LINKAT
#endif
#ifdef CAP_PREAD
/* Existence of CAP_PREAD implies new-style CAP_SEEK semantics */
#define CAP_SEEK_ASWAS 0
#else
/* Old-style CAP_SEEK semantics */
#define CAP_SEEK_ASWAS CAP_SEEK
#define CAP_PREAD CAP_READ
#define CAP_PWRITE CAP_WRITE
#endif
#ifndef CAP_MMAP_R
#define CAP_MMAP_R (CAP_READ|CAP_MMAP)
#define CAP_MMAP_W (CAP_WRITE|CAP_MMAP)
#define CAP_MMAP_X (CAP_MAPEXEC|CAP_MMAP)
#define CAP_MMAP_RW (CAP_MMAP_R|CAP_MMAP_W)
#define CAP_MMAP_RX (CAP_MMAP_R|CAP_MMAP_X)
#define CAP_MMAP_WX (CAP_MMAP_W|CAP_MMAP_X)
#define CAP_MMAP_RWX (CAP_MMAP_R|CAP_MMAP_W|CAP_MMAP_X)
#endif
#ifndef CAP_MKFIFOAT
#define CAP_MKFIFOAT CAP_MKFIFO
#endif
#ifndef CAP_MKNODAT
#define CAP_MKNODAT CAP_MKFIFOAT
#endif
#ifndef CAP_MKDIRAT
#define CAP_MKDIRAT CAP_MKDIR
#endif
#ifndef CAP_UNLINKAT
#define CAP_UNLINKAT CAP_RMDIR
#endif
#ifndef CAP_SOCK_CLIENT
#define CAP_SOCK_CLIENT \
(CAP_CONNECT | CAP_GETPEERNAME | CAP_GETSOCKNAME | CAP_GETSOCKOPT | \
CAP_PEELOFF | CAP_READ | CAP_WRITE | CAP_SETSOCKOPT | CAP_SHUTDOWN)
#endif
#ifndef CAP_SOCK_SERVER
#define CAP_SOCK_SERVER \
(CAP_ACCEPT | CAP_BIND | CAP_GETPEERNAME | CAP_GETSOCKNAME | \
CAP_GETSOCKOPT | CAP_LISTEN | CAP_PEELOFF | CAP_READ | CAP_WRITE | \
CAP_SETSOCKOPT | CAP_SHUTDOWN)
#endif
#ifndef CAP_EVENT
#define CAP_EVENT CAP_POLL_EVENT
#endif
/************************************************************
* Define new-style API functions in terms of old-style API
* functions if absent.
************************************************************/
#ifndef HAVE_CAP_RIGHTS_GET
/* Define cap_rights_get() in terms of old-style cap_getrights() */
inline int cap_rights_get(int fd, cap_rights_t *rights) {
return cap_getrights(fd, rights);
}
#endif
#ifndef HAVE_CAP_RIGHTS_LIMIT
/* Define cap_rights_limit() in terms of old-style cap_new() and dup2() */
#include <unistd.h>
inline int cap_rights_limit(int fd, const cap_rights_t *rights) {
int cap = cap_new(fd, *rights);
if (cap < 0) return cap;
int rc = dup2(cap, fd);
if (rc < 0) return rc;
close(cap);
return rc;
}
#endif
#include <stdio.h>
#ifdef CAP_RIGHTS_VERSION
/* New-style Capsicum API extras for debugging */
static inline void cap_rights_describe(const cap_rights_t *rights, char *buffer) {
int ii;
for (ii = 0; ii < (CAP_RIGHTS_VERSION+2); ii++) {
int len = sprintf(buffer, "0x%016llx ", (unsigned long long)rights->cr_rights[ii]);
buffer += len;
}
}
#ifdef __cplusplus
#include <iostream>
#include <iomanip>
inline std::ostream& operator<<(std::ostream& os, cap_rights_t rights) {
for (int ii = 0; ii < (CAP_RIGHTS_VERSION+2); ii++) {
os << std::hex << std::setw(16) << std::setfill('0') << (unsigned long long)rights.cr_rights[ii] << " ";
}
return os;
}
#endif
#else
static inline void cap_rights_describe(const cap_rights_t *rights, char *buffer) {
sprintf(buffer, "0x%016llx", (*rights));
}
#endif /* new/old style rights manipulation */
#ifdef __cplusplus
#include <string>
extern std::string capsicum_test_bindir;
#endif
#endif /*__CAPSICUM_H__*/

View File

@ -0,0 +1,411 @@
// Test that fcntl works in capability mode.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <string>
#include <map>
#include "capsicum.h"
#include "capsicum-test.h"
#include "syscalls.h"
// Ensure that fcntl() works consistently for both regular file descriptors and
// capability-wrapped ones.
FORK_TEST(Fcntl, Basic) {
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
typedef std::map<std::string, int> FileMap;
// Open some files of different types, and wrap them in capabilities.
FileMap files;
files["file"] = open("/etc/passwd", O_RDONLY);
EXPECT_OK(files["file"]);
files["socket"] = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(files["socket"]);
char shm_name[128];
sprintf(shm_name, "/capsicum-test-%d", getuid());
files["SHM"] = shm_open(shm_name, (O_CREAT|O_RDWR), 0600);
if ((files["SHM"] == -1) && errno == ENOSYS) {
// shm_open() is not implemented in user-mode Linux.
files.erase("SHM");
} else {
EXPECT_OK(files["SHM"]);
}
FileMap caps;
for (FileMap::iterator ii = files.begin(); ii != files.end(); ++ii) {
std::string key = ii->first + " cap";
caps[key] = dup(ii->second);
EXPECT_OK(cap_rights_limit(caps[key], &rights));
EXPECT_OK(caps[key]) << " on " << ii->first;
}
FileMap all(files);
all.insert(files.begin(), files.end());
EXPECT_OK(cap_enter()); // Enter capability mode.
// Ensure that we can fcntl() all the files that we opened above.
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
for (FileMap::iterator ii = all.begin(); ii != all.end(); ++ii) {
EXPECT_OK(fcntl(ii->second, F_GETFL, 0)) << " on " << ii->first;
int cap = dup(ii->second);
EXPECT_OK(cap) << " on " << ii->first;
EXPECT_OK(cap_rights_limit(cap, &r_ro)) << " on " << ii->first;
EXPECT_EQ(-1, fcntl(cap, F_GETFL, 0)) << " on " << ii->first;
EXPECT_EQ(ENOTCAPABLE, errno) << " on " << ii->first;
close(cap);
}
for (FileMap::iterator ii = all.begin(); ii != all.end(); ++ii) {
close(ii->second);
}
shm_unlink(shm_name);
}
// Supported fcntl(2) operations:
// FreeBSD10 FreeBSD9.1: Linux: Rights: Summary:
// F_DUPFD F_DUPFD F_DUPFD NONE as dup(2)
// F_DUPFD_CLOEXEC F_DUPFD_CLOEXEC NONE as dup(2) with close-on-exec
// F_DUP2FD F_DUP2FD NONE as dup2(2)
// F_DUP2FD_CLOEXEC NONE as dup2(2) with close-on-exec
// F_GETFD F_GETFD F_GETFD NONE get close-on-exec flag
// F_SETFD F_SETFD F_SETFD NONE set close-on-exec flag
// * F_GETFL F_GETFL F_GETFL FCNTL get file status flag
// * F_SETFL F_SETFL F_SETFL FCNTL set file status flag
// * F_GETOWN F_GETOWN F_GETOWN FCNTL get pid receiving SIGIO/SIGURG
// * F_SETOWN F_SETOWN F_SETOWN FCNTL set pid receiving SIGIO/SIGURG
// * F_GETOWN_EX FCNTL get pid/thread receiving SIGIO/SIGURG
// * F_SETOWN_EX FCNTL set pid/thread receiving SIGIO/SIGURG
// F_GETLK F_GETLK F_GETLK FLOCK get lock info
// F_SETLK F_SETLK F_SETLK FLOCK set lock info
// F_SETLK_REMOTE FLOCK set lock info
// F_SETLKW F_SETLKW F_SETLKW FLOCK set lock info (blocking)
// F_READAHEAD F_READAHEAD NONE set or clear readahead amount
// F_RDAHEAD F_RDAHEAD NONE set or clear readahead amount to 128KB
// F_GETSIG POLL_EVENT+FSIGNAL get signal sent when I/O possible
// F_SETSIG POLL_EVENT+FSIGNAL set signal sent when I/O possible
// F_GETLEASE FLOCK+FSIGNAL get lease on file descriptor
// F_SETLEASE FLOCK+FSIGNAL set new lease on file descriptor
// F_NOTIFY NOTIFY generate signal on changes (dnotify)
// F_GETPIPE_SZ GETSOCKOPT get pipe size
// F_SETPIPE_SZ SETSOCKOPT set pipe size
// F_GET_SEAL FSTAT get memfd seals
// F_ADD_SEAL FCHMOD set memfd seal
// If HAVE_CAP_FCNTLS_LIMIT is defined, then fcntl(2) operations that require
// CAP_FCNTL (marked with * above) can be further limited with cap_fcntls_limit(2).
namespace {
#define FCNTL_NUM_RIGHTS 9
cap_rights_t fcntl_rights[FCNTL_NUM_RIGHTS];
void InitRights() {
cap_rights_init(&(fcntl_rights[0]), 0); // Later code assumes this is at [0]
cap_rights_init(&(fcntl_rights[1]), CAP_READ, CAP_WRITE);
cap_rights_init(&(fcntl_rights[2]), CAP_FCNTL);
cap_rights_init(&(fcntl_rights[3]), CAP_FLOCK);
#ifdef CAP_FSIGNAL
cap_rights_init(&(fcntl_rights[4]), CAP_EVENT, CAP_FSIGNAL);
cap_rights_init(&(fcntl_rights[5]), CAP_FLOCK, CAP_FSIGNAL);
#else
cap_rights_init(&(fcntl_rights[4]), 0);
cap_rights_init(&(fcntl_rights[5]), 0);
#endif
#ifdef CAP_NOTIFY
cap_rights_init(&(fcntl_rights[6]), CAP_NOTIFY);
#else
cap_rights_init(&(fcntl_rights[6]), 0);
#endif
cap_rights_init(&(fcntl_rights[7]), CAP_SETSOCKOPT);
cap_rights_init(&(fcntl_rights[8]), CAP_GETSOCKOPT);
}
int CheckFcntl(unsigned long long right, int caps[FCNTL_NUM_RIGHTS], int cmd, long arg, const char* context) {
SCOPED_TRACE(context);
cap_rights_t rights;
cap_rights_init(&rights, right);
int ok_index = -1;
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
if (cap_rights_contains(&(fcntl_rights[ii]), &rights)) {
if (ok_index == -1) ok_index = ii;
continue;
}
EXPECT_NOTCAPABLE(fcntl(caps[ii], cmd, arg));
}
EXPECT_NE(-1, ok_index);
int rc = fcntl(caps[ok_index], cmd, arg);
EXPECT_OK(rc);
return rc;
}
} // namespace
#define CHECK_FCNTL(right, caps, cmd, arg) \
CheckFcntl(right, caps, cmd, arg, "fcntl(" #cmd ") expect " #right)
TEST(Fcntl, Commands) {
InitRights();
int fd = open(TmpFile("cap_fcntl_cmds"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
int sock = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(sock);
int caps[FCNTL_NUM_RIGHTS];
int sock_caps[FCNTL_NUM_RIGHTS];
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
caps[ii] = dup(fd);
EXPECT_OK(caps[ii]);
EXPECT_OK(cap_rights_limit(caps[ii], &(fcntl_rights[ii])));
sock_caps[ii] = dup(sock);
EXPECT_OK(sock_caps[ii]);
EXPECT_OK(cap_rights_limit(sock_caps[ii], &(fcntl_rights[ii])));
}
// Check the things that need no rights against caps[0].
int newfd = fcntl(caps[0], F_DUPFD, 0);
EXPECT_OK(newfd);
// dup()'ed FD should have same rights.
cap_rights_t rights;
cap_rights_init(&rights, 0);
EXPECT_OK(cap_rights_get(newfd, &rights));
EXPECT_RIGHTS_EQ(&(fcntl_rights[0]), &rights);
close(newfd);
#ifdef HAVE_F_DUP2FD
EXPECT_OK(fcntl(caps[0], F_DUP2FD, newfd));
// dup2()'ed FD should have same rights.
EXPECT_OK(cap_rights_get(newfd, &rights));
EXPECT_RIGHTS_EQ(&(fcntl_rights[0]), &rights);
close(newfd);
#endif
EXPECT_OK(fcntl(caps[0], F_GETFD, 0));
EXPECT_OK(fcntl(caps[0], F_SETFD, 0));
// Check operations that need CAP_FCNTL.
int fd_flag = CHECK_FCNTL(CAP_FCNTL, caps, F_GETFL, 0);
EXPECT_EQ(0, CHECK_FCNTL(CAP_FCNTL, caps, F_SETFL, fd_flag));
int owner = CHECK_FCNTL(CAP_FCNTL, sock_caps, F_GETOWN, 0);
EXPECT_EQ(0, CHECK_FCNTL(CAP_FCNTL, sock_caps, F_SETOWN, owner));
// Check an operation needing CAP_FLOCK.
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_EQ(0, CHECK_FCNTL(CAP_FLOCK, caps, F_GETLK, (long)&fl));
for (int ii = 0; ii < FCNTL_NUM_RIGHTS; ++ii) {
close(sock_caps[ii]);
close(caps[ii]);
}
close(sock);
close(fd);
unlink(TmpFile("cap_fcntl_cmds"));
}
TEST(Fcntl, WriteLock) {
int fd = open(TmpFile("cap_fcntl_readlock"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
int cap = dup(fd);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL, CAP_READ, CAP_WRITE, CAP_FLOCK);
EXPECT_OK(cap_rights_limit(cap, &rights));
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
// Write-Lock
EXPECT_OK(fcntl(cap, F_SETLK, (long)&fl));
// Check write-locked (from another process).
pid_t child = fork();
if (child == 0) {
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_OK(fcntl(fd, F_GETLK, (long)&fl));
EXPECT_NE(F_UNLCK, fl.l_type);
exit(HasFailure());
}
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
// Unlock
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
EXPECT_OK(fcntl(cap, F_SETLK, (long)&fl));
close(cap);
close(fd);
unlink(TmpFile("cap_fcntl_readlock"));
}
#ifdef HAVE_CAP_FCNTLS_LIMIT
TEST(Fcntl, SubRightNormalFD) {
int fd = open(TmpFile("cap_fcntl_subrightnorm"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
// Restrict the fcntl(2) subrights of a normal FD.
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
int fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_NOTCAPABLE(fcntl(fd, F_SETFL, fd_flag));
// Expect to have all capabilities.
cap_rights_t rights;
EXPECT_OK(cap_rights_get(fd, &rights));
cap_rights_t all;
CAP_SET_ALL(&all);
EXPECT_RIGHTS_EQ(&all, &rights);
cap_fcntl_t fcntls;
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// Can't widen the subrights.
EXPECT_NOTCAPABLE(cap_fcntls_limit(fd, CAP_FCNTL_GETFL|CAP_FCNTL_SETFL));
close(fd);
unlink(TmpFile("cap_fcntl_subrightnorm"));
}
TEST(Fcntl, PreserveSubRights) {
int fd = open(TmpFile("cap_fcntl_subrightpreserve"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
cap_rights_t cur_rights;
cap_fcntl_t fcntls;
EXPECT_OK(cap_rights_get(fd, &cur_rights));
EXPECT_RIGHTS_EQ(&rights, &cur_rights);
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// Limiting the top-level rights leaves the subrights unaffected...
cap_rights_clear(&rights, CAP_READ);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
// ... until we remove CAP_FCNTL.
cap_rights_clear(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)0, fcntls);
EXPECT_EQ(-1, cap_fcntls_limit(fd, CAP_FCNTL_GETFL));
close(fd);
unlink(TmpFile("cap_fcntl_subrightpreserve"));
}
TEST(Fcntl, FLSubRights) {
int fd = open(TmpFile("cap_fcntl_subrights"), O_RDWR|O_CREAT, 0644);
EXPECT_OK(fd);
write(fd, "TEST", 4);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
// Check operations that need CAP_FCNTL with subrights pristine => OK.
int fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_OK(fcntl(fd, F_SETFL, fd_flag));
// Check operations that need CAP_FCNTL with all subrights => OK.
EXPECT_OK(cap_fcntls_limit(fd, CAP_FCNTL_ALL));
fd_flag = fcntl(fd, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_OK(fcntl(fd, F_SETFL, fd_flag));
// Check operations that need CAP_FCNTL with specific subrights.
int fd_get = dup(fd);
int fd_set = dup(fd);
EXPECT_OK(cap_fcntls_limit(fd_get, CAP_FCNTL_GETFL));
EXPECT_OK(cap_fcntls_limit(fd_set, CAP_FCNTL_SETFL));
fd_flag = fcntl(fd_get, F_GETFL, 0);
EXPECT_OK(fd_flag);
EXPECT_NOTCAPABLE(fcntl(fd_set, F_GETFL, 0));
EXPECT_OK(fcntl(fd_set, F_SETFL, fd_flag));
EXPECT_NOTCAPABLE(fcntl(fd_get, F_SETFL, fd_flag));
close(fd_get);
close(fd_set);
// Check operations that need CAP_FCNTL with no subrights => ENOTCAPABLE.
EXPECT_OK(cap_fcntls_limit(fd, 0));
EXPECT_NOTCAPABLE(fcntl(fd, F_GETFL, 0));
EXPECT_NOTCAPABLE(fcntl(fd, F_SETFL, fd_flag));
close(fd);
unlink(TmpFile("cap_fcntl_subrights"));
}
TEST(Fcntl, OWNSubRights) {
int sock = socket(PF_LOCAL, SOCK_STREAM, 0);
EXPECT_OK(sock);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FCNTL);
EXPECT_OK(cap_rights_limit(sock, &rights));
// Check operations that need CAP_FCNTL with no subrights => OK.
int owner = fcntl(sock, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_OK(fcntl(sock, F_SETOWN, owner));
// Check operations that need CAP_FCNTL with all subrights => OK.
EXPECT_OK(cap_fcntls_limit(sock, CAP_FCNTL_ALL));
owner = fcntl(sock, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_OK(fcntl(sock, F_SETOWN, owner));
// Check operations that need CAP_FCNTL with specific subrights.
int sock_get = dup(sock);
int sock_set = dup(sock);
EXPECT_OK(cap_fcntls_limit(sock_get, CAP_FCNTL_GETOWN));
EXPECT_OK(cap_fcntls_limit(sock_set, CAP_FCNTL_SETOWN));
owner = fcntl(sock_get, F_GETOWN, 0);
EXPECT_OK(owner);
EXPECT_NOTCAPABLE(fcntl(sock_set, F_GETOWN, 0));
EXPECT_OK(fcntl(sock_set, F_SETOWN, owner));
EXPECT_NOTCAPABLE(fcntl(sock_get, F_SETOWN, owner));
// Also check we can retrieve the subrights.
cap_fcntl_t fcntls;
EXPECT_OK(cap_fcntls_get(sock_get, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETOWN, fcntls);
EXPECT_OK(cap_fcntls_get(sock_set, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_SETOWN, fcntls);
// And that we can't widen the subrights.
EXPECT_NOTCAPABLE(cap_fcntls_limit(sock_get, CAP_FCNTL_GETOWN|CAP_FCNTL_SETOWN));
EXPECT_NOTCAPABLE(cap_fcntls_limit(sock_set, CAP_FCNTL_GETOWN|CAP_FCNTL_SETOWN));
close(sock_get);
close(sock_set);
// Check operations that need CAP_FCNTL with no subrights => ENOTCAPABLE.
EXPECT_OK(cap_fcntls_limit(sock, 0));
EXPECT_NOTCAPABLE(fcntl(sock, F_GETOWN, 0));
EXPECT_NOTCAPABLE(fcntl(sock, F_SETOWN, owner));
close(sock);
}
#endif

View File

@ -0,0 +1,208 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sstream>
#include "syscalls.h"
#include "capsicum.h"
#include "capsicum-test.h"
// Arguments to use in execve() calls.
static char* null_envp[] = {NULL};
class Execve : public ::testing::Test {
public:
Execve() : exec_fd_(-1) {
// We need a program to exec(), but for fexecve() to work in capability
// mode that program needs to be statically linked (otherwise ld.so will
// attempt to traverse the filesystem to load (e.g.) /lib/libc.so and
// fail).
exec_prog_ = capsicum_test_bindir + "/mini-me";
exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec";
exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid";
exec_fd_ = open(exec_prog_.c_str(), O_RDONLY);
if (exec_fd_ < 0) {
fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str());
}
argv_checkroot_[0] = (char*)exec_prog_.c_str();
argv_fail_[0] = (char*)exec_prog_.c_str();
argv_pass_[0] = (char*)exec_prog_.c_str();
}
~Execve() {
if (exec_fd_ >= 0) {
close(exec_fd_);
exec_fd_ = -1;
}
}
protected:
char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr};
char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr};
char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr};
std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_;
int exec_fd_;
};
class Fexecve : public Execve {
public:
Fexecve() : Execve() {}
};
class FexecveWithScript : public Fexecve {
public:
FexecveWithScript() :
Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {}
void SetUp() override {
// First, build an executable shell script
int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755);
EXPECT_OK(fd);
const char* contents = "#!/bin/sh\nexit 99\n";
EXPECT_OK(write(fd, contents, strlen(contents)));
close(fd);
}
void TearDown() override {
(void)::unlink(temp_script_filename_);
}
const char *temp_script_filename_;
};
FORK_TEST_F(Execve, BasicFexecve) {
EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
// Should not reach here, exec() takes over.
EXPECT_TRUE(!"fexecve() should never return");
}
FORK_TEST_F(Execve, InCapMode) {
EXPECT_OK(cap_enter());
EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
// Should not reach here, exec() takes over.
EXPECT_TRUE(!"fexecve() should never return");
}
FORK_TEST_F(Execve, FailWithoutCap) {
EXPECT_OK(cap_enter());
int cap_fd = dup(exec_fd_);
EXPECT_OK(cap_fd);
cap_rights_t rights;
cap_rights_init(&rights, 0);
EXPECT_OK(cap_rights_limit(cap_fd, &rights));
EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp));
EXPECT_EQ(ENOTCAPABLE, errno);
}
FORK_TEST_F(Execve, SucceedWithCap) {
EXPECT_OK(cap_enter());
int cap_fd = dup(exec_fd_);
EXPECT_OK(cap_fd);
cap_rights_t rights;
// TODO(drysdale): would prefer that Linux Capsicum not need all of these
// rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable.
cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ);
EXPECT_OK(cap_rights_limit(cap_fd, &rights));
EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp));
// Should not reach here, exec() takes over.
EXPECT_TRUE(!"fexecve() should have succeeded");
}
FORK_TEST_F(Fexecve, ExecutePermissionCheck) {
int fd = open(exec_prog_noexec_.c_str(), O_RDONLY);
EXPECT_OK(fd);
if (fd >= 0) {
struct stat data;
EXPECT_OK(fstat(fd, &data));
EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH));
EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp));
EXPECT_EQ(EACCES, errno);
close(fd);
}
}
FORK_TEST_F(Fexecve, SetuidIgnored) {
if (geteuid() == 0) {
TEST_SKIPPED("requires non-root");
return;
}
int fd = open(exec_prog_setuid_.c_str(), O_RDONLY);
EXPECT_OK(fd);
EXPECT_OK(cap_enter());
if (fd >= 0) {
struct stat data;
EXPECT_OK(fstat(fd, &data));
EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID);
EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp));
// Should not reach here, exec() takes over.
EXPECT_TRUE(!"fexecve() should have succeeded");
close(fd);
}
}
FORK_TEST_F(Fexecve, ExecveFailure) {
EXPECT_OK(cap_enter());
EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp));
EXPECT_EQ(ECAPMODE, errno);
}
FORK_TEST_F(FexecveWithScript, CapModeScriptFail) {
int fd;
// Open the script file, with CAP_FEXECVE rights.
fd = open(temp_script_filename_, O_RDONLY);
cap_rights_t rights;
cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK);
EXPECT_OK(cap_rights_limit(fd, &rights));
EXPECT_OK(cap_enter()); // Enter capability mode
// Attempt fexecve; should fail, because "/bin/sh" is inaccessible.
EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp));
}
#ifdef HAVE_EXECVEAT
class Execveat : public Execve {
public:
Execveat() : Execve() {}
};
TEST_F(Execveat, NoUpwardTraversal) {
char *abspath = realpath(exec_prog_, NULL);
char cwd[1024];
getcwd(cwd, sizeof(cwd));
int dfd = open(".", O_DIRECTORY|O_RDONLY);
pid_t child = fork();
if (child == 0) {
EXPECT_OK(cap_enter()); // Enter capability mode.
// Can't execveat() an absolute path, even relative to a dfd.
EXPECT_SYSCALL_FAIL(ECAPMODE,
execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0));
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
execveat(dfd, abspath, argv_pass_, null_envp, 0));
// Can't execveat() a relative path ("../<dir>/./<exe>").
char *p = cwd + strlen(cwd);
while (*p != '/') p--;
char buffer[1024] = "../";
strcat(buffer, ++p);
strcat(buffer, "/");
strcat(buffer, exec_prog_);
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
execveat(dfd, buffer, argv_pass_, null_envp, 0));
exit(HasFailure() ? 99 : 123);
}
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status;
EXPECT_EQ(123, WEXITSTATUS(status));
free(abspath);
close(dfd);
}
#endif

View File

@ -0,0 +1,234 @@
// Test that ioctl works in capability mode.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "capsicum.h"
#include "capsicum-test.h"
// Ensure that ioctl() works consistently for both regular file descriptors and
// capability-wrapped ones.
TEST(Ioctl, Basic) {
cap_rights_t rights_ioctl;
cap_rights_init(&rights_ioctl, CAP_IOCTL);
cap_rights_t rights_many;
cap_rights_init(&rights_many, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FSTAT, CAP_FSYNC);
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
int fd_no = dup(fd);
EXPECT_OK(fd_no);
EXPECT_OK(cap_rights_limit(fd, &rights_ioctl));
EXPECT_OK(cap_rights_limit(fd_no, &rights_many));
// Check that CAP_IOCTL is required.
int bytes;
EXPECT_OK(ioctl(fd, FIONREAD, &bytes));
EXPECT_NOTCAPABLE(ioctl(fd_no, FIONREAD, &bytes));
int one = 1;
EXPECT_OK(ioctl(fd, FIOCLEX, &one));
EXPECT_NOTCAPABLE(ioctl(fd_no, FIOCLEX, &one));
close(fd);
close(fd_no);
}
#ifdef HAVE_CAP_IOCTLS_LIMIT
TEST(Ioctl, SubRightNormalFD) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
// Restrict the ioctl(2) subrights of a normal FD.
cap_ioctl_t ioctl_nread = FIONREAD;
EXPECT_OK(cap_ioctls_limit(fd, &ioctl_nread, 1));
int bytes;
EXPECT_OK(ioctl(fd, FIONREAD, &bytes));
int one = 1;
EXPECT_NOTCAPABLE(ioctl(fd, FIOCLEX, &one));
// Expect to have all primary rights.
cap_rights_t rights;
EXPECT_OK(cap_rights_get(fd, &rights));
cap_rights_t all;
CAP_SET_ALL(&all);
EXPECT_RIGHTS_EQ(&all, &rights);
cap_ioctl_t ioctls[16];
memset(ioctls, 0, sizeof(ioctls));
ssize_t nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
// Can't widen the subrights.
cap_ioctl_t both_ioctls[2] = {FIONREAD, FIOCLEX};
EXPECT_NOTCAPABLE(cap_ioctls_limit(fd, both_ioctls, 2));
close(fd);
}
TEST(Ioctl, PreserveSubRights) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_IOCTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
cap_ioctl_t ioctl_nread = FIONREAD;
EXPECT_OK(cap_ioctls_limit(fd, &ioctl_nread, 1));
cap_rights_t cur_rights;
cap_ioctl_t ioctls[16];
ssize_t nioctls;
EXPECT_OK(cap_rights_get(fd, &cur_rights));
EXPECT_RIGHTS_EQ(&rights, &cur_rights);
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
// Limiting the top-level rights leaves the subrights unaffected...
cap_rights_clear(&rights, CAP_READ);
EXPECT_OK(cap_rights_limit(fd, &rights));
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
// ... until we remove CAP_IOCTL
cap_rights_clear(&rights, CAP_IOCTL);
EXPECT_OK(cap_rights_limit(fd, &rights));
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(0, nioctls);
EXPECT_EQ(-1, cap_ioctls_limit(fd, &ioctl_nread, 1));
close(fd);
}
TEST(Ioctl, SubRights) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
cap_ioctl_t ioctls[16];
ssize_t nioctls;
memset(ioctls, 0, sizeof(ioctls));
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(CAP_IOCTLS_ALL, nioctls);
cap_rights_t rights_ioctl;
cap_rights_init(&rights_ioctl, CAP_IOCTL);
EXPECT_OK(cap_rights_limit(fd, &rights_ioctl));
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(CAP_IOCTLS_ALL, nioctls);
// Check operations that need CAP_IOCTL with subrights pristine => OK.
int bytes;
EXPECT_OK(ioctl(fd, FIONREAD, &bytes));
int one = 1;
EXPECT_OK(ioctl(fd, FIOCLEX, &one));
// Check operations that need CAP_IOCTL with all relevant subrights => OK.
cap_ioctl_t both_ioctls[2] = {FIONREAD, FIOCLEX};
EXPECT_OK(cap_ioctls_limit(fd, both_ioctls, 2));
EXPECT_OK(ioctl(fd, FIONREAD, &bytes));
EXPECT_OK(ioctl(fd, FIOCLEX, &one));
// Check what happens if we ask for subrights but don't have the space for them.
cap_ioctl_t before = 0xBBBBBBBB;
cap_ioctl_t one_ioctl = 0;
cap_ioctl_t after = 0xAAAAAAAA;
nioctls = cap_ioctls_get(fd, &one_ioctl, 1);
EXPECT_EQ(2, nioctls);
EXPECT_EQ(0xBBBBBBBB, before);
EXPECT_TRUE(one_ioctl == FIONREAD || one_ioctl == FIOCLEX);
EXPECT_EQ(0xAAAAAAAA, after);
// Check operations that need CAP_IOCTL with particular subrights.
int fd_nread = dup(fd);
int fd_clex = dup(fd);
cap_ioctl_t ioctl_nread = FIONREAD;
cap_ioctl_t ioctl_clex = FIOCLEX;
EXPECT_OK(cap_ioctls_limit(fd_nread, &ioctl_nread, 1));
EXPECT_OK(cap_ioctls_limit(fd_clex, &ioctl_clex, 1));
EXPECT_OK(ioctl(fd_nread, FIONREAD, &bytes));
EXPECT_NOTCAPABLE(ioctl(fd_clex, FIONREAD, &bytes));
EXPECT_OK(ioctl(fd_clex, FIOCLEX, &one));
EXPECT_NOTCAPABLE(ioctl(fd_nread, FIOCLEX, &one));
// Also check we can retrieve the subrights.
memset(ioctls, 0, sizeof(ioctls));
nioctls = cap_ioctls_get(fd_nread, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
memset(ioctls, 0, sizeof(ioctls));
nioctls = cap_ioctls_get(fd_clex, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIOCLEX, ioctls[0]);
// And that we can't widen the subrights.
EXPECT_NOTCAPABLE(cap_ioctls_limit(fd_nread, both_ioctls, 2));
EXPECT_NOTCAPABLE(cap_ioctls_limit(fd_clex, both_ioctls, 2));
close(fd_nread);
close(fd_clex);
// Check operations that need CAP_IOCTL with no subrights => ENOTCAPABLE.
EXPECT_OK(cap_ioctls_limit(fd, NULL, 0));
EXPECT_NOTCAPABLE(ioctl(fd, FIONREAD, &bytes));
EXPECT_NOTCAPABLE(ioctl(fd, FIOCLEX, &one));
close(fd);
}
#ifdef CAP_IOCTLS_LIMIT_MAX
TEST(Ioctl, TooManySubRights) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
cap_ioctl_t ioctls[CAP_IOCTLS_LIMIT_MAX + 1];
for (int ii = 0; ii <= CAP_IOCTLS_LIMIT_MAX; ii++) {
ioctls[ii] = ii + 1;
}
cap_rights_t rights_ioctl;
cap_rights_init(&rights_ioctl, CAP_IOCTL);
EXPECT_OK(cap_rights_limit(fd, &rights_ioctl));
// Can only limit to a certain number of ioctls
EXPECT_EQ(-1, cap_ioctls_limit(fd, ioctls, CAP_IOCTLS_LIMIT_MAX + 1));
EXPECT_EQ(EINVAL, errno);
EXPECT_OK(cap_ioctls_limit(fd, ioctls, CAP_IOCTLS_LIMIT_MAX));
close(fd);
}
#else
TEST(Ioctl, ManySubRights) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
const int nioctls = 150000;
cap_ioctl_t* ioctls = (cap_ioctl_t*)calloc(nioctls, sizeof(cap_ioctl_t));
for (int ii = 0; ii < nioctls; ii++) {
ioctls[ii] = ii + 1;
}
cap_rights_t rights_ioctl;
cap_rights_init(&rights_ioctl, CAP_IOCTL);
EXPECT_OK(cap_rights_limit(fd, &rights_ioctl));
EXPECT_OK(cap_ioctls_limit(fd, ioctls, nioctls));
// Limit to a subset; if this takes a long time then there's an
// O(N^2) implementation of the ioctl list comparison.
EXPECT_OK(cap_ioctls_limit(fd, ioctls, nioctls - 1));
close(fd);
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
all: capsicum-test smoketest mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS)
OBJECTS=capsicum-test-main.o capsicum-test.o capability-fd.o fexecve.o procdesc.o capmode.o fcntl.o ioctl.o openat.o sysctl.o select.o mqueue.o socket.o sctp.o capability-fd-pair.o linux.o overhead.o rename.o
GTEST_DIR=gtest-1.8.1
GTEST_INCS=-I$(GTEST_DIR)/include -I$(GTEST_DIR)
GTEST_FLAGS=-DGTEST_USE_OWN_TR1_TUPLE=1 -DGTEST_HAS_TR1_TUPLE=1
CXXFLAGS+=$(ARCHFLAG) -Wall -g $(GTEST_INCS) $(GTEST_FLAGS) --std=c++11
CFLAGS+=$(ARCHFLAG) -Wall -g
capsicum-test: $(OBJECTS) libgtest.a $(LOCAL_LIBS)
$(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS)
# Small statically-linked program for fexecve tests
# (needs to be statically linked so that execve()ing it
# doesn't involve ld.so traversing the filesystem).
mini-me: mini-me.c
$(CC) $(CFLAGS) -static -o $@ $<
mini-me.noexec: mini-me
cp mini-me $@ && chmod -x $@
mini-me.setuid: mini-me
rm -f $@ && cp mini-me $@&& sudo chown root $@ && sudo chmod u+s $@
# Simple C test of Capsicum syscalls
SMOKETEST_OBJECTS=smoketest.o
smoketest: $(SMOKETEST_OBJECTS) $(LOCAL_LIBS)
$(CC) $(CFLAGS) -o $@ $(SMOKETEST_OBJECTS) $(LIBCAPRIGHTS)
test: capsicum-test mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS)
./capsicum-test
gtest-all.o:
$(CXX) $(ARCHFLAG) -I$(GTEST_DIR)/include -I$(GTEST_DIR) $(GTEST_FLAGS) -c ${GTEST_DIR}/src/gtest-all.cc
libgtest.a: gtest-all.o
$(AR) -rv libgtest.a gtest-all.o
clean:
rm -rf gtest-all.o libgtest.a capsicum-test mini-me mini-me.noexec smoketest $(SMOKETEST_OBJECTS) $(OBJECTS) $(LOCAL_CLEAN) $(EXTRA_PROGS)

View File

@ -0,0 +1,38 @@
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc == 2 && !strcmp(argv[1], "--pass")) {
fprintf(stderr,"[%d] %s immediately returning 0\n", getpid(), argv[0]);
return 0;
}
if (argc == 2 && !strcmp(argv[1], "--fail")) {
fprintf(stderr,"[%d] %s immediately returning 1\n", getpid(), argv[0]);
return 1;
}
if (argc == 2 && !strcmp(argv[1], "--checkroot")) {
int rc = (geteuid() == 0);
fprintf(stderr,"[uid:%d] %s immediately returning (geteuid() == 0) = %d\n", geteuid(), argv[0], rc);
return rc;
}
if (argc == 2 && !strcmp(argv[1], "--capmode")) {
/* Expect to already be in capability mode: check we can't open a file */
int rc = 0;
int fd = open("/etc/passwd", O_RDONLY);
if (fd > 0) {
fprintf(stderr,"[%d] %s unexpectedly able to open file\n", getpid(), argv[0]);
rc = 1;
}
fprintf(stderr,"[%d] %s --capmode returning %d\n", getpid(), argv[0], rc);
return rc;
}
return -1;
}

View File

@ -0,0 +1,100 @@
// Tests for POSIX message queue functionality.
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <string>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
// Run a test case in a forked process, possibly cleaning up a
// message after completion
#define FORK_TEST_ON_MQ(test_case_name, test_name, test_mq) \
static void test_case_name##_##test_name##_ForkTest(); \
TEST(test_case_name, test_name ## Forked) { \
_RUN_FORKED_FN(test_case_name##_##test_name##_ForkTest, \
#test_case_name, #test_name); \
const char *mqname = test_mq; \
if (mqname) mq_unlink_(mqname); \
} \
static void test_case_name##_##test_name##_ForkTest()
static bool invoked;
void seen_it_done_it(int) {
invoked = true;
}
FORK_TEST_ON_MQ(PosixMqueue, CapMode, "/cap_mq") {
int mq = mq_open_("/cap_mq", O_RDWR|O_CREAT, 0644, NULL);
// On FreeBSD, turn on message queue support with:
// - 'kldload mqueuefs'
// - 'options P1003_1B_MQUEUE' in kernel build config.
if (mq < 0 && errno == ENOSYS) {
TEST_SKIPPED("mq_open -> -ENOSYS");
return;
}
EXPECT_OK(mq);
cap_rights_t r_read;
cap_rights_init(&r_read, CAP_READ);
cap_rights_t r_write;
cap_rights_init(&r_write, CAP_WRITE);
cap_rights_t r_poll;
cap_rights_init(&r_poll, CAP_EVENT);
int cap_read_mq = dup(mq);
EXPECT_OK(cap_read_mq);
EXPECT_OK(cap_rights_limit(cap_read_mq, &r_read));
int cap_write_mq = dup(mq);
EXPECT_OK(cap_write_mq);
EXPECT_OK(cap_rights_limit(cap_write_mq, &r_write));
int cap_poll_mq = dup(mq);
EXPECT_OK(cap_poll_mq);
EXPECT_OK(cap_rights_limit(cap_poll_mq, &r_poll));
EXPECT_OK(mq_close_(mq));
signal(SIGUSR2, seen_it_done_it);
EXPECT_OK(cap_enter()); // Enter capability mode
// Can no longer access the message queue via the POSIX IPC namespace.
EXPECT_CAPMODE(mq_open_("/cap_mw", O_RDWR|O_CREAT, 0644, NULL));
struct sigevent se;
se.sigev_notify = SIGEV_SIGNAL;
se.sigev_signo = SIGUSR2;
EXPECT_OK(mq_notify_(cap_poll_mq, &se));
EXPECT_NOTCAPABLE(mq_notify_(cap_read_mq, &se));
EXPECT_NOTCAPABLE(mq_notify_(cap_write_mq, &se));
const unsigned int kPriority = 10;
const char* message = "xyzzy";
struct timespec ts;
ts.tv_sec = 1;
ts.tv_nsec = 0;
EXPECT_OK(mq_timedsend_(cap_write_mq, message, strlen(message) + 1, kPriority, &ts));
EXPECT_NOTCAPABLE(mq_timedsend_(cap_read_mq, message, strlen(message) + 1, kPriority, &ts));
sleep(1); // Give the notification a chance to arrive.
EXPECT_TRUE(invoked);
struct mq_attr mqa;
EXPECT_OK(mq_getattr_(cap_poll_mq, &mqa));
EXPECT_OK(mq_setattr_(cap_poll_mq, &mqa, NULL));
EXPECT_NOTCAPABLE(mq_getattr_(cap_write_mq, &mqa));
char* buffer = (char *)malloc(mqa.mq_msgsize);
unsigned int priority;
EXPECT_NOTCAPABLE(mq_timedreceive_(cap_write_mq, buffer, mqa.mq_msgsize, &priority, &ts));
EXPECT_OK(mq_timedreceive_(cap_read_mq, buffer, mqa.mq_msgsize, &priority, &ts));
EXPECT_EQ(std::string(message), std::string(buffer));
EXPECT_EQ(kPriority, priority);
free(buffer);
close(cap_read_mq);
close(cap_write_mq);
close(cap_poll_mq);
}

View File

@ -0,0 +1,361 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string>
#include "capsicum.h"
#include "capsicum-test.h"
#include "syscalls.h"
// Check an open call works and close the resulting fd.
#define EXPECT_OPEN_OK(f) do { \
int _fd = f; \
EXPECT_OK(_fd); \
close(_fd); \
} while (0)
static void CreateFile(const char *filename, const char *contents) {
int fd = open(filename, O_CREAT|O_RDWR, 0644);
EXPECT_OK(fd);
EXPECT_OK(write(fd, contents, strlen(contents)));
close(fd);
}
// Test openat(2) in a variety of sitations to ensure that it obeys Capsicum
// "strict relative" rules:
//
// 1. Use strict relative lookups in capability mode or when operating
// relative to a capability.
// 2. When performing strict relative lookups, absolute paths (including
// symlinks to absolute paths) are not allowed, nor are paths containing
// '..' components.
//
// These rules apply when:
// - the directory FD is a Capsicum capability
// - the process is in capability mode
// - the openat(2) operation includes the O_BENEATH flag.
FORK_TEST(Openat, Relative) {
int etc = open("/etc/", O_RDONLY);
EXPECT_OK(etc);
cap_rights_t r_base;
cap_rights_init(&r_base, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_LOOKUP, CAP_FCNTL, CAP_IOCTL);
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ);
cap_rights_t r_rl;
cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP);
int etc_cap = dup(etc);
EXPECT_OK(etc_cap);
EXPECT_OK(cap_rights_limit(etc_cap, &r_ro));
int etc_cap_ro = dup(etc);
EXPECT_OK(etc_cap_ro);
EXPECT_OK(cap_rights_limit(etc_cap_ro, &r_rl));
int etc_cap_base = dup(etc);
EXPECT_OK(etc_cap_base);
EXPECT_OK(cap_rights_limit(etc_cap_base, &r_base));
#ifdef HAVE_CAP_FCNTLS_LIMIT
// Also limit fcntl(2) subrights.
EXPECT_OK(cap_fcntls_limit(etc_cap_base, CAP_FCNTL_GETFL));
#endif
#ifdef HAVE_CAP_IOCTLS_LIMIT
// Also limit ioctl(2) subrights.
cap_ioctl_t ioctl_nread = FIONREAD;
EXPECT_OK(cap_ioctls_limit(etc_cap_base, &ioctl_nread, 1));
#endif
// openat(2) with regular file descriptors in non-capability mode
// Should Just Work (tm).
EXPECT_OPEN_OK(openat(etc, "/etc/passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc, "../etc/passwd", O_RDONLY));
// Lookups relative to capabilities should be strictly relative.
// When not in capability mode, we don't actually require CAP_LOOKUP.
EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY));
// Performing openat(2) on a path with leading slash ignores
// the provided directory FD.
EXPECT_OPEN_OK(openat(etc_cap_ro, "/etc/passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_base, "/etc/passwd", O_RDONLY));
// Relative lookups that go upward are not allowed.
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY);
// A file opened relative to a capability should itself be a capability.
int fd = openat(etc_cap_base, "passwd", O_RDONLY);
EXPECT_OK(fd);
cap_rights_t rights;
EXPECT_OK(cap_rights_get(fd, &rights));
EXPECT_RIGHTS_IN(&rights, &r_base);
#ifdef HAVE_CAP_FCNTLS_LIMIT
cap_fcntl_t fcntls;
EXPECT_OK(cap_fcntls_get(fd, &fcntls));
EXPECT_EQ((cap_fcntl_t)CAP_FCNTL_GETFL, fcntls);
#endif
#ifdef HAVE_CAP_IOCTLS_LIMIT
cap_ioctl_t ioctls[16];
ssize_t nioctls;
memset(ioctls, 0, sizeof(ioctls));
nioctls = cap_ioctls_get(fd, ioctls, 16);
EXPECT_OK(nioctls);
EXPECT_EQ(1, nioctls);
EXPECT_EQ((cap_ioctl_t)FIONREAD, ioctls[0]);
#endif
close(fd);
// Enter capability mode; now ALL lookups are strictly relative.
EXPECT_OK(cap_enter());
// Relative lookups on regular files or capabilities with CAP_LOOKUP
// ought to succeed.
EXPECT_OPEN_OK(openat(etc, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_ro, "passwd", O_RDONLY));
EXPECT_OPEN_OK(openat(etc_cap_base, "passwd", O_RDONLY));
// Lookup relative to capabilities without CAP_LOOKUP should fail.
EXPECT_NOTCAPABLE(openat(etc_cap, "passwd", O_RDONLY));
// Absolute lookups should fail.
EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "/etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "/etc/passwd", O_RDONLY);
// Lookups containing '..' should fail in capability mode.
EXPECT_OPENAT_FAIL_TRAVERSAL(etc, "../etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_ro, "../etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(etc_cap_base, "../etc/passwd", O_RDONLY);
fd = openat(etc, "passwd", O_RDONLY);
EXPECT_OK(fd);
// A file opened relative to a capability should itself be a capability.
fd = openat(etc_cap_base, "passwd", O_RDONLY);
EXPECT_OK(fd);
EXPECT_OK(cap_rights_get(fd, &rights));
EXPECT_RIGHTS_IN(&rights, &r_base);
close(fd);
fd = openat(etc_cap_ro, "passwd", O_RDONLY);
EXPECT_OK(fd);
EXPECT_OK(cap_rights_get(fd, &rights));
EXPECT_RIGHTS_IN(&rights, &r_rl);
close(fd);
}
#define TOPDIR "cap_topdir"
#define SUBDIR TOPDIR "/subdir"
class OpenatTest : public ::testing::Test {
public:
// Build a collection of files, subdirs and symlinks:
// /tmp/cap_topdir/
// /topfile
// /subdir/
// /subdir/bottomfile
// /symlink.samedir -> topfile
// /dsymlink.samedir -> ./
// /symlink.down -> subdir/bottomfile
// /dsymlink.down -> subdir/
// /symlink.absolute_out -> /etc/passwd
// /dsymlink.absolute_out -> /etc/
// /symlink.relative_in -> ../../tmp/cap_topdir/topfile
// /dsymlink.relative_in -> ../../tmp/cap_topdir/
// /symlink.relative_out -> ../../etc/passwd
// /dsymlink.relative_out -> ../../etc/
// /subdir/dsymlink.absolute_in -> /tmp/cap_topdir/
// /subdir/dsymlink.up -> ../
// /subdir/symlink.absolute_in -> /tmp/cap_topdir/topfile
// /subdir/symlink.up -> ../topfile
// (In practice, this is a little more complicated because tmpdir might
// not be "/tmp".)
OpenatTest() {
// Create a couple of nested directories
int rc = mkdir(TmpFile(TOPDIR), 0755);
EXPECT_OK(rc);
if (rc < 0) {
EXPECT_EQ(EEXIST, errno);
}
rc = mkdir(TmpFile(SUBDIR), 0755);
EXPECT_OK(rc);
if (rc < 0) {
EXPECT_EQ(EEXIST, errno);
}
// Figure out a path prefix (like "../..") that gets us to the root
// directory from TmpFile(TOPDIR).
const char *p = TmpFile(TOPDIR); // maybe "/tmp/somewhere/cap_topdir"
std::string dots2root = "..";
while (*p++ != '\0') {
if (*p == '/') {
dots2root += "/..";
}
}
// Create normal files in each.
CreateFile(TmpFile(TOPDIR "/topfile"), "Top-level file");
CreateFile(TmpFile(SUBDIR "/bottomfile"), "File in subdirectory");
// Create various symlinks to files.
EXPECT_OK(symlink("topfile", TmpFile(TOPDIR "/symlink.samedir")));
EXPECT_OK(symlink("subdir/bottomfile", TmpFile(TOPDIR "/symlink.down")));
EXPECT_OK(symlink(TmpFile(TOPDIR "/topfile"), TmpFile(SUBDIR "/symlink.absolute_in")));
EXPECT_OK(symlink("/etc/passwd", TmpFile(TOPDIR "/symlink.absolute_out")));
std::string dots2top = dots2root + TmpFile(TOPDIR "/topfile");
EXPECT_OK(symlink(dots2top.c_str(), TmpFile(TOPDIR "/symlink.relative_in")));
std::string dots2passwd = dots2root + "/etc/passwd";
EXPECT_OK(symlink(dots2passwd.c_str(), TmpFile(TOPDIR "/symlink.relative_out")));
EXPECT_OK(symlink("../topfile", TmpFile(SUBDIR "/symlink.up")));
// Create various symlinks to directories.
EXPECT_OK(symlink("./", TmpFile(TOPDIR "/dsymlink.samedir")));
EXPECT_OK(symlink("subdir/", TmpFile(TOPDIR "/dsymlink.down")));
EXPECT_OK(symlink(TmpFile(TOPDIR "/"), TmpFile(SUBDIR "/dsymlink.absolute_in")));
EXPECT_OK(symlink("/etc/", TmpFile(TOPDIR "/dsymlink.absolute_out")));
std::string dots2cwd = dots2root + tmpdir + "/";
EXPECT_OK(symlink(dots2cwd.c_str(), TmpFile(TOPDIR "/dsymlink.relative_in")));
std::string dots2etc = dots2root + "/etc/";
EXPECT_OK(symlink(dots2etc.c_str(), TmpFile(TOPDIR "/dsymlink.relative_out")));
EXPECT_OK(symlink("../", TmpFile(SUBDIR "/dsymlink.up")));
// Open directory FDs for those directories and for cwd.
dir_fd_ = open(TmpFile(TOPDIR), O_RDONLY);
EXPECT_OK(dir_fd_);
sub_fd_ = open(TmpFile(SUBDIR), O_RDONLY);
EXPECT_OK(sub_fd_);
cwd_ = openat(AT_FDCWD, ".", O_RDONLY);
EXPECT_OK(cwd_);
// Move into the directory for the test.
EXPECT_OK(fchdir(dir_fd_));
}
~OpenatTest() {
fchdir(cwd_);
close(cwd_);
close(sub_fd_);
close(dir_fd_);
unlink(TmpFile(SUBDIR "/symlink.up"));
unlink(TmpFile(SUBDIR "/symlink.absolute_in"));
unlink(TmpFile(TOPDIR "/symlink.absolute_out"));
unlink(TmpFile(TOPDIR "/symlink.relative_in"));
unlink(TmpFile(TOPDIR "/symlink.relative_out"));
unlink(TmpFile(TOPDIR "/symlink.down"));
unlink(TmpFile(TOPDIR "/symlink.samedir"));
unlink(TmpFile(SUBDIR "/dsymlink.up"));
unlink(TmpFile(SUBDIR "/dsymlink.absolute_in"));
unlink(TmpFile(TOPDIR "/dsymlink.absolute_out"));
unlink(TmpFile(TOPDIR "/dsymlink.relative_in"));
unlink(TmpFile(TOPDIR "/dsymlink.relative_out"));
unlink(TmpFile(TOPDIR "/dsymlink.down"));
unlink(TmpFile(TOPDIR "/dsymlink.samedir"));
unlink(TmpFile(SUBDIR "/bottomfile"));
unlink(TmpFile(TOPDIR "/topfile"));
rmdir(TmpFile(SUBDIR));
rmdir(TmpFile(TOPDIR));
}
// Check openat(2) policing that is common across capabilities, capability mode and O_BENEATH.
void CheckPolicing(int oflag) {
// OK for normal access.
EXPECT_OPEN_OK(openat(dir_fd_, "topfile", O_RDONLY|oflag));
EXPECT_OPEN_OK(openat(dir_fd_, "subdir/bottomfile", O_RDONLY|oflag));
EXPECT_OPEN_OK(openat(sub_fd_, "bottomfile", O_RDONLY|oflag));
EXPECT_OPEN_OK(openat(sub_fd_, ".", O_RDONLY|oflag));
// Can't open paths with ".." in them.
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../topfile", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "../subdir/bottomfile", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "..", O_RDONLY|oflag);
#ifdef HAVE_OPENAT_INTERMEDIATE_DOTDOT
// OK for dotdot lookups that don't escape the top directory
EXPECT_OPEN_OK(openat(dir_fd_, "subdir/../topfile", O_RDONLY|oflag));
#endif
// Check that we can't escape the top directory by the cunning
// ruse of going via a subdirectory.
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "subdir/../../etc/passwd", O_RDONLY|oflag);
// Should only be able to open symlinks that stay within the directory.
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY|oflag));
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY|oflag));
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.absolute_out", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_in", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "symlink.relative_out", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.absolute_in", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "symlink.up", O_RDONLY|oflag);
EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.samedir/topfile", O_RDONLY|oflag));
EXPECT_OPEN_OK(openat(dir_fd_, "dsymlink.down/bottomfile", O_RDONLY|oflag));
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.absolute_out/passwd", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_in/topfile", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "dsymlink.relative_out/passwd", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.absolute_in/topfile", O_RDONLY|oflag);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "dsymlink.up/topfile", O_RDONLY|oflag);
// Although recall that O_NOFOLLOW prevents symlink following in final component.
EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.samedir", O_RDONLY|O_NOFOLLOW|oflag));
EXPECT_SYSCALL_FAIL(E_TOO_MANY_LINKS, openat(dir_fd_, "symlink.down", O_RDONLY|O_NOFOLLOW|oflag));
}
protected:
int dir_fd_;
int sub_fd_;
int cwd_;
};
TEST_F(OpenatTest, WithCapability) {
// Any kind of symlink can be opened relative to an ordinary directory FD.
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.samedir", O_RDONLY));
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.down", O_RDONLY));
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.absolute_out", O_RDONLY));
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_in", O_RDONLY));
EXPECT_OPEN_OK(openat(dir_fd_, "symlink.relative_out", O_RDONLY));
EXPECT_OPEN_OK(openat(sub_fd_, "symlink.absolute_in", O_RDONLY));
EXPECT_OPEN_OK(openat(sub_fd_, "symlink.up", O_RDONLY));
// Now make both DFDs into Capsicum capabilities.
cap_rights_t r_rl;
cap_rights_init(&r_rl, CAP_READ, CAP_LOOKUP, CAP_FCHDIR);
EXPECT_OK(cap_rights_limit(dir_fd_, &r_rl));
EXPECT_OK(cap_rights_limit(sub_fd_, &r_rl));
CheckPolicing(0);
// Use of AT_FDCWD is independent of use of a capability.
// Can open paths starting with "/" against a capability dfd, because the dfd is ignored.
}
FORK_TEST_F(OpenatTest, InCapabilityMode) {
EXPECT_OK(cap_enter()); // Enter capability mode
CheckPolicing(0);
// Use of AT_FDCWD is banned in capability mode.
EXPECT_CAPMODE(openat(AT_FDCWD, "topfile", O_RDONLY));
EXPECT_CAPMODE(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY));
EXPECT_CAPMODE(openat(AT_FDCWD, "/etc/passwd", O_RDONLY));
// Can't open paths starting with "/" in capability mode.
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY);
}
#ifdef O_BENEATH
TEST_F(OpenatTest, WithFlag) {
CheckPolicing(O_BENEATH);
// Check with AT_FDCWD.
EXPECT_OPEN_OK(openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH));
EXPECT_OPEN_OK(openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY|O_BENEATH));
// Can't open paths starting with "/" with O_BENEATH specified.
EXPECT_OPENAT_FAIL_TRAVERSAL(AT_FDCWD, "/etc/passwd", O_RDONLY|O_BENEATH);
EXPECT_OPENAT_FAIL_TRAVERSAL(dir_fd_, "/etc/passwd", O_RDONLY|O_BENEATH);
EXPECT_OPENAT_FAIL_TRAVERSAL(sub_fd_, "/etc/passwd", O_RDONLY|O_BENEATH);
}
FORK_TEST_F(OpenatTest, WithFlagInCapabilityMode) {
EXPECT_OK(cap_enter()); // Enter capability mode
CheckPolicing(O_BENEATH);
}
#endif

View File

@ -0,0 +1,45 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
#ifdef HAVE_SYSCALL
double RepeatSyscall(int count, int nr, long arg1, long arg2, long arg3) {
const clock_t t0 = clock(); // or gettimeofday or whatever
for (int ii = 0; ii < count; ii++) {
syscall(nr, arg1, arg2, arg3);
}
const clock_t t1 = clock();
return (t1 - t0) / (double)CLOCKS_PER_SEC;
}
typedef int (*EntryFn)(void);
double CompareSyscall(EntryFn entry_fn, int count, int nr,
long arg1, long arg2, long arg3) {
double bare = RepeatSyscall(count, nr, arg1, arg2, arg3);
EXPECT_OK(entry_fn());
double capmode = RepeatSyscall(count, nr, arg1, arg2, arg3);
if (verbose) fprintf(stderr, "%d iterations bare=%fs capmode=%fs ratio=%.2f%%\n",
count, bare, capmode, 100.0*capmode/bare);
if (bare==0.0) {
if (capmode==0.0) return 1.0;
return 999.0;
}
return capmode/bare;
}
FORK_TEST(Overhead, GetTid) {
EXPECT_GT(10, CompareSyscall(&cap_enter, 10000, __NR_gettid, 0, 0, 0));
}
FORK_TEST(Overhead, Seek) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_GT(50, CompareSyscall(&cap_enter, 10000, __NR_lseek, fd, 0, SEEK_SET));
close(fd);
}
#endif

View File

@ -0,0 +1,977 @@
// Tests for the process descriptor API for Linux.
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <iomanip>
#include <map>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
#ifndef __WALL
// Linux requires __WALL in order for waitpid(specific_pid,...) to
// see and reap any specific pid. Define this to nothing for platforms
// (FreeBSD) where it doesn't exist, to reduce macroing.
#define __WALL 0
#endif
// TODO(drysdale): it would be nice to use proper synchronization between
// processes, rather than synchronization-via-sleep; faster too.
//------------------------------------------------
// Utilities for the tests.
static pid_t pdwait4_(int pd, int *status, int options, struct rusage *ru) {
#ifdef HAVE_PDWAIT4
return pdwait4(pd, status, options, ru);
#else
// Simulate pdwait4() with wait4(pdgetpid()); this won't work in capability mode.
pid_t pid = -1;
int rc = pdgetpid(pd, &pid);
if (rc < 0) {
return rc;
}
options |= __WALL;
return wait4(pid, status, options, ru);
#endif
}
static void print_rusage(FILE *f, struct rusage *ru) {
fprintf(f, " User CPU time=%ld.%06ld\n", (long)ru->ru_utime.tv_sec, (long)ru->ru_utime.tv_usec);
fprintf(f, " System CPU time=%ld.%06ld\n", (long)ru->ru_stime.tv_sec, (long)ru->ru_stime.tv_usec);
fprintf(f, " Max RSS=%ld\n", ru->ru_maxrss);
}
static void print_stat(FILE *f, const struct stat *stat) {
fprintf(f,
"{ .st_dev=%ld, st_ino=%ld, st_mode=%04o, st_nlink=%ld, st_uid=%d, st_gid=%d,\n"
" .st_rdev=%ld, .st_size=%ld, st_blksize=%ld, .st_block=%ld,\n "
#ifdef HAVE_STAT_BIRTHTIME
".st_birthtime=%ld, "
#endif
".st_atime=%ld, .st_mtime=%ld, .st_ctime=%ld}\n",
(long)stat->st_dev, (long)stat->st_ino, stat->st_mode,
(long)stat->st_nlink, stat->st_uid, stat->st_gid,
(long)stat->st_rdev, (long)stat->st_size, (long)stat->st_blksize,
(long)stat->st_blocks,
#ifdef HAVE_STAT_BIRTHTIME
(long)stat->st_birthtime,
#endif
(long)stat->st_atime, (long)stat->st_mtime, (long)stat->st_ctime);
}
static std::map<int,bool> had_signal;
static void handle_signal(int x) {
had_signal[x] = true;
}
// Check that the given child process terminates as expected.
void CheckChildFinished(pid_t pid, bool signaled=false) {
// Wait for the child to finish.
int rc;
int status = 0;
do {
rc = waitpid(pid, &status, __WALL);
if (rc < 0) {
fprintf(stderr, "Warning: waitpid error %s (%d)\n", strerror(errno), errno);
ADD_FAILURE() << "Failed to wait for child";
break;
} else if (rc == pid) {
break;
}
} while (true);
EXPECT_EQ(pid, rc);
if (rc == pid) {
if (signaled) {
EXPECT_TRUE(WIFSIGNALED(status));
} else {
EXPECT_TRUE(WIFEXITED(status)) << std::hex << status;
EXPECT_EQ(0, WEXITSTATUS(status));
}
}
}
//------------------------------------------------
// Basic tests of process descriptor functionality
TEST(Pdfork, Simple) {
int pd = -1;
pid_t parent = getpid_();
int pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: check pid values.
EXPECT_EQ(-1, pd);
EXPECT_NE(parent, getpid_());
EXPECT_EQ(parent, getppid());
sleep(1);
exit(0);
}
usleep(100); // ensure the child has a chance to run
EXPECT_NE(-1, pd);
EXPECT_PID_ALIVE(pid);
int pid_got;
EXPECT_OK(pdgetpid(pd, &pid_got));
EXPECT_EQ(pid, pid_got);
// Wait long enough for the child to exit().
sleep(2);
EXPECT_PID_ZOMBIE(pid);
// Wait for the the child.
int status;
struct rusage ru;
memset(&ru, 0, sizeof(ru));
int waitrc = pdwait4_(pd, &status, 0, &ru);
EXPECT_EQ(pid, waitrc);
if (verbose) {
fprintf(stderr, "For pd %d pid %d:\n", pd, pid);
print_rusage(stderr, &ru);
}
EXPECT_PID_GONE(pid);
// Can only pdwait4(pd) once (as initial call reaps zombie).
memset(&ru, 0, sizeof(ru));
EXPECT_EQ(-1, pdwait4_(pd, &status, 0, &ru));
EXPECT_EQ(ECHILD, errno);
EXPECT_OK(close(pd));
}
TEST(Pdfork, InvalidFlag) {
int pd = -1;
int pid = pdfork(&pd, PD_DAEMON<<5);
if (pid == 0) {
exit(1);
}
EXPECT_EQ(-1, pid);
EXPECT_EQ(EINVAL, errno);
if (pid > 0) waitpid(pid, NULL, __WALL);
}
TEST(Pdfork, TimeCheck) {
time_t now = time(NULL); // seconds since epoch
EXPECT_NE(-1, now);
if (verbose) fprintf(stderr, "Calling pdfork around %ld\n", (long)(long)now);
int pd = -1;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: check we didn't get a valid process descriptor then exit.
EXPECT_EQ(-1, pdgetpid(pd, &pid));
EXPECT_EQ(EBADF, errno);
exit(HasFailure());
}
#ifdef HAVE_PROCDESC_FSTAT
// Parent process. Ensure that [acm]times have been set correctly.
struct stat stat;
memset(&stat, 0, sizeof(stat));
EXPECT_OK(fstat(pd, &stat));
if (verbose) print_stat(stderr, &stat);
#ifdef HAVE_STAT_BIRTHTIME
EXPECT_GE(now, stat.st_birthtime);
EXPECT_EQ(stat.st_birthtime, stat.st_atime);
#endif
EXPECT_LT((now - stat.st_atime), 2);
EXPECT_EQ(stat.st_atime, stat.st_ctime);
EXPECT_EQ(stat.st_ctime, stat.st_mtime);
#endif
// Wait for the child to finish.
pid_t pd_pid = -1;
EXPECT_OK(pdgetpid(pd, &pd_pid));
EXPECT_EQ(pid, pd_pid);
CheckChildFinished(pid);
}
TEST(Pdfork, UseDescriptor) {
int pd = -1;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: immediately exit
exit(0);
}
CheckChildFinished(pid);
}
TEST(Pdfork, NonProcessDescriptor) {
int fd = open("/etc/passwd", O_RDONLY);
EXPECT_OK(fd);
// pd*() operations should fail on a non-process descriptor.
EXPECT_EQ(-1, pdkill(fd, SIGUSR1));
int status;
EXPECT_EQ(-1, pdwait4_(fd, &status, 0, NULL));
pid_t pid;
EXPECT_EQ(-1, pdgetpid(fd, &pid));
close(fd);
}
static void *SubThreadMain(void *) {
while (true) {
if (verbose) fprintf(stderr, " subthread: \"I aten't dead\"\n");
usleep(100000);
}
return NULL;
}
static void *ThreadMain(void *) {
int pd;
pid_t child = pdfork(&pd, 0);
if (child == 0) {
// Child: start a subthread then loop
pthread_t child_subthread;
EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain, NULL));
while (true) {
if (verbose) fprintf(stderr, " pdforked process %d: \"I aten't dead\"\n", getpid());
usleep(100000);
}
exit(0);
}
if (verbose) fprintf(stderr, " thread generated pd %d\n", pd);
sleep(2);
// Pass the process descriptor back to the main thread.
return reinterpret_cast<void *>(pd);
}
TEST(Pdfork, FromThread) {
// Fire off a new thread to do all of the creation work.
pthread_t child_thread;
EXPECT_OK(pthread_create(&child_thread, NULL, ThreadMain, NULL));
void *data;
EXPECT_OK(pthread_join(child_thread, &data));
int pd = reinterpret_cast<intptr_t>(data);
if (verbose) fprintf(stderr, "retrieved pd %d from terminated thread\n", pd);
// Kill and reap.
pid_t pid;
EXPECT_OK(pdgetpid(pd, &pid));
EXPECT_OK(pdkill(pd, SIGKILL));
int status;
EXPECT_EQ(pid, pdwait4_(pd, &status, 0, NULL));
EXPECT_TRUE(WIFSIGNALED(status));
}
//------------------------------------------------
// More complicated tests.
// Test fixture that pdfork()s off a child process, which terminates
// when it receives anything on a pipe.
class PipePdforkBase : public ::testing::Test {
public:
PipePdforkBase(int pdfork_flags) : pd_(-1), pid_(-1) {
had_signal.clear();
int pipes[2];
EXPECT_OK(pipe(pipes));
pipe_ = pipes[1];
int parent = getpid_();
if (verbose) fprintf(stderr, "[%d] about to pdfork()\n", getpid_());
int rc = pdfork(&pd_, pdfork_flags);
EXPECT_OK(rc);
if (rc == 0) {
// Child process: blocking-read an int from the pipe then exit with that value.
EXPECT_NE(parent, getpid_());
EXPECT_EQ(parent, getppid());
if (verbose) fprintf(stderr, " [%d] child of %d waiting for value on pipe\n", getpid_(), getppid());
read(pipes[0], &rc, sizeof(rc));
if (verbose) fprintf(stderr, " [%d] got value %d on pipe, exiting\n", getpid_(), rc);
exit(rc);
}
pid_ = rc;
usleep(100); // ensure the child has a chance to run
}
~PipePdforkBase() {
// Terminate by any means necessary.
if (pd_ > 0) {
pdkill(pd_, SIGKILL);
close(pd_);
}
if (pid_ > 0) {
kill(pid_, SIGKILL);
waitpid(pid_, NULL, __WALL|WNOHANG);
}
// Check signal expectations.
EXPECT_FALSE(had_signal[SIGCHLD]);
}
int TerminateChild() {
// Tell the child to exit.
int zero = 0;
if (verbose) fprintf(stderr, "[%d] write 0 to pipe\n", getpid_());
return write(pipe_, &zero, sizeof(zero));
}
protected:
int pd_;
int pipe_;
pid_t pid_;
};
class PipePdfork : public PipePdforkBase {
public:
PipePdfork() : PipePdforkBase(0) {}
};
class PipePdforkDaemon : public PipePdforkBase {
public:
PipePdforkDaemon() : PipePdforkBase(PD_DAEMON) {}
};
// Can we poll a process descriptor?
TEST_F(PipePdfork, Poll) {
// Poll the process descriptor, nothing happening.
struct pollfd fdp;
fdp.fd = pd_;
fdp.events = POLLIN | POLLERR | POLLHUP;
fdp.revents = 0;
EXPECT_EQ(0, poll(&fdp, 1, 0));
TerminateChild();
// Poll again, should have activity on the process descriptor.
EXPECT_EQ(1, poll(&fdp, 1, 2000));
EXPECT_TRUE(fdp.revents & POLLHUP);
// Poll a third time, still have POLLHUP.
fdp.revents = 0;
EXPECT_EQ(1, poll(&fdp, 1, 0));
EXPECT_TRUE(fdp.revents & POLLHUP);
}
// Can multiple processes poll on the same descriptor?
TEST_F(PipePdfork, PollMultiple) {
int child = fork();
EXPECT_OK(child);
if (child == 0) {
// Child: wait to give time for setup, then write to the pipe (which will
// induce exit of the pdfork()ed process) and exit.
sleep(1);
TerminateChild();
exit(0);
}
usleep(100); // ensure the child has a chance to run
// Fork again
int doppel = fork();
EXPECT_OK(doppel);
// We now have:
// pid A: main process, here
// |--pid B: pdfork()ed process, blocked on read()
// |--pid C: fork()ed process, in sleep(1) above
// +--pid D: doppel process, here
// Both A and D execute the following code.
// First, check no activity on the process descriptor yet.
struct pollfd fdp;
fdp.fd = pd_;
fdp.events = POLLIN | POLLERR | POLLHUP;
fdp.revents = 0;
EXPECT_EQ(0, poll(&fdp, 1, 0));
// Now, wait (indefinitely) for activity on the process descriptor.
// We expect:
// - pid C will finish its sleep, write to the pipe and exit
// - pid B will unblock from read(), and exit
// - this will generate an event on the process descriptor...
// - ...in both process A and process D.
EXPECT_EQ(1, poll(&fdp, 1, 2000));
EXPECT_TRUE(fdp.revents & POLLHUP);
if (doppel == 0) {
// Child: process D exits.
exit(0);
} else {
// Parent: wait on process D.
int rc = 0;
waitpid(doppel, &rc, __WALL);
EXPECT_TRUE(WIFEXITED(rc));
EXPECT_EQ(0, WEXITSTATUS(rc));
// Also wait on process B.
CheckChildFinished(child);
}
}
// Check that exit status/rusage for a dead pdfork()ed child can be retrieved
// via any process descriptor, multiple times.
TEST_F(PipePdfork, MultipleRetrieveExitStatus) {
EXPECT_PID_ALIVE(pid_);
int pd_copy = dup(pd_);
EXPECT_LT(0, TerminateChild());
int status;
struct rusage ru;
memset(&ru, 0, sizeof(ru));
int waitrc = pdwait4_(pd_copy, &status, 0, &ru);
EXPECT_EQ(pid_, waitrc);
if (verbose) {
fprintf(stderr, "For pd %d -> pid %d:\n", pd_, pid_);
print_rusage(stderr, &ru);
}
EXPECT_PID_GONE(pid_);
#ifdef NOTYET
// Child has been reaped, so original process descriptor dangles but
// still has access to rusage information.
memset(&ru, 0, sizeof(ru));
EXPECT_EQ(0, pdwait4_(pd_, &status, 0, &ru));
#endif
close(pd_copy);
}
TEST_F(PipePdfork, ChildExit) {
EXPECT_PID_ALIVE(pid_);
EXPECT_LT(0, TerminateChild());
EXPECT_PID_DEAD(pid_);
int status;
int rc = pdwait4_(pd_, &status, 0, NULL);
EXPECT_OK(rc);
EXPECT_EQ(pid_, rc);
pid_ = 0;
}
#ifdef HAVE_PROC_FDINFO
TEST_F(PipePdfork, FdInfo) {
char buffer[1024];
sprintf(buffer, "/proc/%d/fdinfo/%d", getpid_(), pd_);
int procfd = open(buffer, O_RDONLY);
EXPECT_OK(procfd);
EXPECT_OK(read(procfd, buffer, sizeof(buffer)));
// The fdinfo should include the file pos of the underlying file
EXPECT_NE((char*)NULL, strstr(buffer, "pos:\t0")) << buffer;
// ...and the underlying pid
char pidline[256];
sprintf(pidline, "pid:\t%d", pid_);
EXPECT_NE((char*)NULL, strstr(buffer, pidline)) << buffer;
close(procfd);
}
#endif
// Closing a normal process descriptor terminates the underlying process.
TEST_F(PipePdfork, Close) {
sighandler_t original = signal(SIGCHLD, handle_signal);
EXPECT_PID_ALIVE(pid_);
int status;
EXPECT_EQ(0, waitpid(pid_, &status, __WALL|WNOHANG));
EXPECT_OK(close(pd_));
pd_ = -1;
EXPECT_FALSE(had_signal[SIGCHLD]);
EXPECT_PID_DEAD(pid_);
#ifdef __FreeBSD__
EXPECT_EQ(-1, waitpid(pid_, NULL, __WALL));
EXPECT_EQ(errno, ECHILD);
#else
// Having closed the process descriptor means that pdwait4(pd) now doesn't work.
int rc = pdwait4_(pd_, &status, 0, NULL);
EXPECT_EQ(-1, rc);
EXPECT_EQ(EBADF, errno);
// Closing all process descriptors means the the child can only be reaped via pid.
EXPECT_EQ(pid_, waitpid(pid_, &status, __WALL|WNOHANG));
#endif
signal(SIGCHLD, original);
}
TEST_F(PipePdfork, CloseLast) {
sighandler_t original = signal(SIGCHLD, handle_signal);
// Child should only die when last process descriptor is closed.
EXPECT_PID_ALIVE(pid_);
int pd_other = dup(pd_);
EXPECT_OK(close(pd_));
pd_ = -1;
EXPECT_PID_ALIVE(pid_);
int status;
EXPECT_EQ(0, waitpid(pid_, &status, __WALL|WNOHANG));
// Can no longer pdwait4() the closed process descriptor...
EXPECT_EQ(-1, pdwait4_(pd_, &status, WNOHANG, NULL));
EXPECT_EQ(EBADF, errno);
// ...but can pdwait4() the still-open process descriptor.
errno = 0;
EXPECT_EQ(0, pdwait4_(pd_other, &status, WNOHANG, NULL));
EXPECT_EQ(0, errno);
EXPECT_OK(close(pd_other));
EXPECT_PID_DEAD(pid_);
EXPECT_FALSE(had_signal[SIGCHLD]);
signal(SIGCHLD, original);
}
FORK_TEST(Pdfork, OtherUser) {
REQUIRE_ROOT();
int pd;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child process: loop forever.
while (true) usleep(100000);
}
usleep(100);
// Now that the second process has been pdfork()ed, change euid.
setuid(other_uid);
if (verbose) fprintf(stderr, "uid=%d euid=%d\n", getuid(), geteuid());
// Fail to kill child with normal PID operation.
EXPECT_EQ(-1, kill(pid, SIGKILL));
EXPECT_EQ(EPERM, errno);
EXPECT_PID_ALIVE(pid);
// Succeed with pdkill though.
EXPECT_OK(pdkill(pd, SIGKILL));
EXPECT_PID_ZOMBIE(pid);
int status;
int rc = pdwait4_(pd, &status, WNOHANG, NULL);
EXPECT_OK(rc);
EXPECT_EQ(pid, rc);
EXPECT_TRUE(WIFSIGNALED(status));
}
TEST_F(PipePdfork, WaitPidThenPd) {
TerminateChild();
int status;
// If we waitpid(pid) first...
int rc = waitpid(pid_, &status, __WALL);
EXPECT_OK(rc);
EXPECT_EQ(pid_, rc);
#ifdef NOTYET
// ...the zombie is reaped but we can still subsequently pdwait4(pd).
EXPECT_EQ(0, pdwait4_(pd_, &status, 0, NULL));
#endif
}
TEST_F(PipePdfork, WaitPdThenPid) {
TerminateChild();
int status;
// If we pdwait4(pd) first...
int rc = pdwait4_(pd_, &status, 0, NULL);
EXPECT_OK(rc);
EXPECT_EQ(pid_, rc);
// ...the zombie is reaped and cannot subsequently waitpid(pid).
EXPECT_EQ(-1, waitpid(pid_, &status, __WALL));
EXPECT_EQ(ECHILD, errno);
}
// Setting PD_DAEMON prevents close() from killing the child.
TEST_F(PipePdforkDaemon, Close) {
EXPECT_OK(close(pd_));
pd_ = -1;
EXPECT_PID_ALIVE(pid_);
// Can still explicitly kill it via the pid.
if (pid_ > 0) {
EXPECT_OK(kill(pid_, SIGKILL));
EXPECT_PID_DEAD(pid_);
}
}
static void TestPdkill(pid_t pid, int pd) {
EXPECT_PID_ALIVE(pid);
// SIGCONT is ignored by default.
EXPECT_OK(pdkill(pd, SIGCONT));
EXPECT_PID_ALIVE(pid);
// SIGINT isn't
EXPECT_OK(pdkill(pd, SIGINT));
EXPECT_PID_DEAD(pid);
// pdkill() on zombie is no-op.
errno = 0;
EXPECT_EQ(0, pdkill(pd, SIGINT));
EXPECT_EQ(0, errno);
// pdkill() on reaped process gives -ESRCH.
CheckChildFinished(pid, true);
EXPECT_EQ(-1, pdkill(pd, SIGINT));
EXPECT_EQ(ESRCH, errno);
}
TEST_F(PipePdfork, Pdkill) {
TestPdkill(pid_, pd_);
}
TEST_F(PipePdforkDaemon, Pdkill) {
TestPdkill(pid_, pd_);
}
TEST(Pdfork, PdkillOtherSignal) {
int pd = -1;
int pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: watch for SIGUSR1 forever.
had_signal.clear();
signal(SIGUSR1, handle_signal);
while (!had_signal[SIGUSR1]) {
usleep(100000);
}
exit(123);
}
sleep(1);
// Send an invalid signal.
EXPECT_EQ(-1, pdkill(pd, 0xFFFF));
EXPECT_EQ(EINVAL, errno);
// Send an expected SIGUSR1 to the pdfork()ed child.
EXPECT_PID_ALIVE(pid);
pdkill(pd, SIGUSR1);
EXPECT_PID_DEAD(pid);
// Child's exit status confirms whether it received the signal.
int status;
int rc = waitpid(pid, &status, __WALL);
EXPECT_OK(rc);
EXPECT_EQ(pid, rc);
EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << rc;
EXPECT_EQ(123, WEXITSTATUS(status));
}
pid_t PdforkParentDeath(int pdfork_flags) {
// Set up:
// pid A: main process, here
// +--pid B: fork()ed process, sleep(4)s then exits
// +--pid C: pdfork()ed process, looping forever
int sock_fds[2];
EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds));
if (verbose) fprintf(stderr, "[%d] parent about to fork()...\n", getpid_());
pid_t child = fork();
EXPECT_OK(child);
if (child == 0) {
int pd;
if (verbose) fprintf(stderr, " [%d] child about to pdfork()...\n", getpid_());
pid_t grandchild = pdfork(&pd, pdfork_flags);
if (grandchild == 0) {
while (true) {
if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", getpid_());
sleep(1);
}
}
if (verbose) fprintf(stderr, " [%d] pdfork()ed grandchild %d, sending ID to parent\n", getpid_(), grandchild);
// send grandchild pid to parent
write(sock_fds[1], &grandchild, sizeof(grandchild));
sleep(4);
if (verbose) fprintf(stderr, " [%d] child terminating\n", getpid_());
exit(0);
}
if (verbose) fprintf(stderr, "[%d] fork()ed child is %d\n", getpid_(), child);
pid_t grandchild;
read(sock_fds[0], &grandchild, sizeof(grandchild));
if (verbose) fprintf(stderr, "[%d] receive grandchild id %d\n", getpid_(), grandchild);
EXPECT_PID_ALIVE(child);
EXPECT_PID_ALIVE(grandchild);
sleep(6);
// Child dies, closing its process descriptor for the grandchild.
EXPECT_PID_DEAD(child);
CheckChildFinished(child);
return grandchild;
}
TEST(Pdfork, Bagpuss) {
// "And of course when Bagpuss goes to sleep, all his friends go to sleep too"
pid_t grandchild = PdforkParentDeath(0);
// By default: child death => closed process descriptor => grandchild death.
EXPECT_PID_DEAD(grandchild);
}
TEST(Pdfork, BagpussDaemon) {
pid_t grandchild = PdforkParentDeath(PD_DAEMON);
// With PD_DAEMON: child death => closed process descriptor => no effect on grandchild.
EXPECT_PID_ALIVE(grandchild);
if (grandchild > 0) {
EXPECT_OK(kill(grandchild, SIGKILL));
}
}
// The exit of a pdfork()ed process should not generate SIGCHLD.
TEST_F(PipePdfork, NoSigchld) {
had_signal.clear();
sighandler_t original = signal(SIGCHLD, handle_signal);
TerminateChild();
int rc = 0;
// Can waitpid() for the specific pid of the pdfork()ed child.
EXPECT_EQ(pid_, waitpid(pid_, &rc, __WALL));
EXPECT_TRUE(WIFEXITED(rc)) << "0x" << std::hex << rc;
EXPECT_FALSE(had_signal[SIGCHLD]);
signal(SIGCHLD, original);
}
// The exit of a pdfork()ed process whose process descriptors have
// all been closed should generate SIGCHLD. The child process needs
// PD_DAEMON to survive the closure of the process descriptors.
TEST_F(PipePdforkDaemon, NoPDSigchld) {
had_signal.clear();
sighandler_t original = signal(SIGCHLD, handle_signal);
EXPECT_OK(close(pd_));
TerminateChild();
#ifdef __FreeBSD__
EXPECT_EQ(-1, waitpid(pid_, NULL, __WALL));
EXPECT_EQ(errno, ECHILD);
#else
int rc = 0;
// Can waitpid() for the specific pid of the pdfork()ed child.
EXPECT_EQ(pid_, waitpid(pid_, &rc, __WALL));
EXPECT_TRUE(WIFEXITED(rc)) << "0x" << std::hex << rc;
#endif
EXPECT_FALSE(had_signal[SIGCHLD]);
signal(SIGCHLD, original);
}
#ifdef HAVE_PROCDESC_FSTAT
TEST_F(PipePdfork, ModeBits) {
// Owner rwx bits indicate liveness of child
struct stat stat;
memset(&stat, 0, sizeof(stat));
EXPECT_OK(fstat(pd_, &stat));
if (verbose) print_stat(stderr, &stat);
EXPECT_EQ(S_IRWXU, (long)(stat.st_mode & S_IRWXU));
TerminateChild();
usleep(100000);
memset(&stat, 0, sizeof(stat));
EXPECT_OK(fstat(pd_, &stat));
if (verbose) print_stat(stderr, &stat);
EXPECT_EQ(0, (int)(stat.st_mode & S_IRWXU));
}
#endif
TEST_F(PipePdfork, WildcardWait) {
// TODO(FreeBSD): make wildcard wait ignore pdfork()ed children
// https://bugs.freebsd.org/201054
TerminateChild();
sleep(1); // Ensure child is truly dead.
// Wildcard waitpid(-1) should not see the pdfork()ed child because
// there is still a process descriptor for it.
int rc;
EXPECT_EQ(-1, waitpid(-1, &rc, WNOHANG));
EXPECT_EQ(ECHILD, errno);
EXPECT_OK(close(pd_));
pd_ = -1;
}
FORK_TEST(Pdfork, Pdkill) {
had_signal.clear();
int pd;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: set a SIGINT handler and sleep.
had_signal.clear();
signal(SIGINT, handle_signal);
if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_());
int left = sleep(10);
if (verbose) fprintf(stderr, "[%d] child slept, %d sec left, had[SIGINT]=%d\n",
getpid_(), left, had_signal[SIGINT]);
// Expect this sleep to be interrupted by the signal (and so left > 0).
exit(left == 0);
}
// Parent: get child's PID.
pid_t pd_pid;
EXPECT_OK(pdgetpid(pd, &pd_pid));
EXPECT_EQ(pid, pd_pid);
// Interrupt the child after a second.
sleep(1);
EXPECT_OK(pdkill(pd, SIGINT));
// Make sure the child finished properly (caught signal then exited).
CheckChildFinished(pid);
}
FORK_TEST(Pdfork, PdkillSignal) {
int pd;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: sleep. No SIGINT handler.
if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_());
int left = sleep(10);
if (verbose) fprintf(stderr, "[%d] child slept, %d sec left\n", getpid_(), left);
exit(99);
}
// Kill the child (as it doesn't handle SIGINT).
sleep(1);
EXPECT_OK(pdkill(pd, SIGINT));
// Make sure the child finished properly (terminated by signal).
CheckChildFinished(pid, true);
}
//------------------------------------------------
// Test interactions with other parts of Capsicum:
// - capability mode
// - capabilities
FORK_TEST(Pdfork, DaemonUnrestricted) {
EXPECT_OK(cap_enter());
int fd;
// Capability mode leaves pdfork() available, with and without flag.
int rc;
rc = pdfork(&fd, PD_DAEMON);
EXPECT_OK(rc);
if (rc == 0) {
// Child: immediately terminate.
exit(0);
}
rc = pdfork(&fd, 0);
EXPECT_OK(rc);
if (rc == 0) {
// Child: immediately terminate.
exit(0);
}
}
TEST(Pdfork, MissingRights) {
pid_t parent = getpid_();
int pd = -1;
pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid);
if (pid == 0) {
// Child: loop forever.
EXPECT_NE(parent, getpid_());
while (true) sleep(1);
}
// Create two capabilities from the process descriptor.
cap_rights_t r_ro;
cap_rights_init(&r_ro, CAP_READ, CAP_LOOKUP);
int cap_incapable = dup(pd);
EXPECT_OK(cap_incapable);
EXPECT_OK(cap_rights_limit(cap_incapable, &r_ro));
cap_rights_t r_pdall;
cap_rights_init(&r_pdall, CAP_PDGETPID, CAP_PDWAIT, CAP_PDKILL);
int cap_capable = dup(pd);
EXPECT_OK(cap_capable);
EXPECT_OK(cap_rights_limit(cap_capable, &r_pdall));
pid_t other_pid;
EXPECT_NOTCAPABLE(pdgetpid(cap_incapable, &other_pid));
EXPECT_NOTCAPABLE(pdkill(cap_incapable, SIGINT));
int status;
EXPECT_NOTCAPABLE(pdwait4_(cap_incapable, &status, 0, NULL));
EXPECT_OK(pdgetpid(cap_capable, &other_pid));
EXPECT_EQ(pid, other_pid);
EXPECT_OK(pdkill(cap_capable, SIGINT));
int rc = pdwait4_(pd, &status, 0, NULL);
EXPECT_OK(rc);
EXPECT_EQ(pid, rc);
}
//------------------------------------------------
// Passing process descriptors between processes.
TEST_F(PipePdfork, PassProcessDescriptor) {
int sock_fds[2];
EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds));
struct msghdr mh;
mh.msg_name = NULL; // No address needed
mh.msg_namelen = 0;
char buffer1[1024];
struct iovec iov[1];
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);
mh.msg_iov = iov;
mh.msg_iovlen = 1;
char buffer2[1024];
mh.msg_control = buffer2;
mh.msg_controllen = sizeof(buffer2);
struct cmsghdr *cmptr;
if (verbose) fprintf(stderr, "[%d] about to fork()\n", getpid_());
pid_t child2 = fork();
if (child2 == 0) {
// Child: close our copy of the original process descriptor.
close(pd_);
// Child: wait to receive process descriptor over socket
if (verbose) fprintf(stderr, " [%d] child of %d waiting for process descriptor on socket\n", getpid_(), getppid());
int rc = recvmsg(sock_fds[0], &mh, 0);
EXPECT_OK(rc);
EXPECT_LE(CMSG_LEN(sizeof(int)), mh.msg_controllen);
cmptr = CMSG_FIRSTHDR(&mh);
int pd = *(int*)CMSG_DATA(cmptr);
EXPECT_EQ(CMSG_LEN(sizeof(int)), cmptr->cmsg_len);
cmptr = CMSG_NXTHDR(&mh, cmptr);
EXPECT_TRUE(cmptr == NULL);
if (verbose) fprintf(stderr, " [%d] got process descriptor %d on socket\n", getpid_(), pd);
// Child: confirm we can do pd*() operations on the process descriptor
pid_t other;
EXPECT_OK(pdgetpid(pd, &other));
if (verbose) fprintf(stderr, " [%d] process descriptor %d is pid %d\n", getpid_(), pd, other);
sleep(2);
if (verbose) fprintf(stderr, " [%d] close process descriptor %d\n", getpid_(), pd);
close(pd);
// Last process descriptor closed, expect death
EXPECT_PID_DEAD(other);
exit(HasFailure());
}
usleep(1000); // Ensure subprocess runs
// Send the process descriptor over the pipe to the sub-process
mh.msg_controllen = CMSG_LEN(sizeof(int));
cmptr = CMSG_FIRSTHDR(&mh);
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
*(int *)CMSG_DATA(cmptr) = pd_;
buffer1[0] = 0;
iov[0].iov_len = 1;
sleep(1);
if (verbose) fprintf(stderr, "[%d] send process descriptor %d on socket\n", getpid_(), pd_);
int rc = sendmsg(sock_fds[1], &mh, 0);
EXPECT_OK(rc);
if (verbose) fprintf(stderr, "[%d] close process descriptor %d\n", getpid_(), pd_);
close(pd_); // Not last open process descriptor
// wait for child2
int status;
EXPECT_EQ(child2, waitpid(child2, &status, __WALL));
rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
// confirm death all round
EXPECT_PID_DEAD(child2);
EXPECT_PID_DEAD(pid_);
}

View File

@ -0,0 +1,49 @@
#include <fcntl.h>
#include <sys/stat.h>
#include "./capsicum-test.h"
// There was a Capsicum-related regression in FreeBSD renameat,
// which affects certain cases independent of Capsicum or capability mode
//
// added to test the renameat syscall for the case that
// - the "to" file already exists
// - the "to" file is specified by an absolute path
// - the "to" file descriptor is used
// (this descriptor should be ignored if absolute path is provided)
//
// details at: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=222258
const char * create_tmp_src(const char* filename) {
const char *src_path = TmpFile(filename);
int src_fd = open(src_path, O_CREAT|O_RDWR, 0644);
close(src_fd);
return src_path;
}
TEST(Rename, AbsDesignationSame) {
const char *src_path = create_tmp_src("rename_test");
EXPECT_OK(rename(src_path, src_path));
unlink(src_path);
}
TEST(RenameAt, AbsDesignationSame) {
const char *src_path = create_tmp_src("renameat_test");
const char *dir_path = TmpFile("renameat_test_dir");
EXPECT_OK(mkdir(dir_path, 0755));
// random temporary directory descriptor
int dfd = open(dir_path, O_DIRECTORY);
// Various rename from/to the same absolute path; in each case the source
// and dest directory FDs should be irrelevant.
EXPECT_OK(renameat(AT_FDCWD, src_path, AT_FDCWD, src_path));
EXPECT_OK(renameat(AT_FDCWD, src_path, dfd, src_path));
EXPECT_OK(renameat(dfd, src_path, AT_FDCWD, src_path));
EXPECT_OK(renameat(dfd, src_path, dfd, src_path));
close(dfd);
rmdir(dir_path);
unlink(src_path);
}

View File

@ -0,0 +1,212 @@
// Tests of SCTP functionality
// Requires: libsctp-dev package on Debian Linux, CONFIG_IP_SCTP in kernel config
#ifdef HAVE_SCTP
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include "syscalls.h"
#include "capsicum.h"
#include "capsicum-test.h"
static cap_rights_t r_ro;
static cap_rights_t r_wo;
static cap_rights_t r_rw;
static cap_rights_t r_all;
static cap_rights_t r_all_nopeel;
#define DO_PEELOFF 0x1A
#define DO_TERM 0x1B
static int SctpClient(int port, unsigned char byte) {
// Create sockets
int sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
EXPECT_OK(sock);
if (sock < 0) return sock;
int cap_sock_ro = dup(sock);
EXPECT_OK(cap_sock_ro);
EXPECT_OK(cap_rights_limit(cap_sock_ro, &r_rw));
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
close(sock);
// Send a message. Requires CAP_WRITE and CAP_CONNECT.
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(port);
EXPECT_NOTCAPABLE(sctp_sendmsg(cap_sock_ro, &byte, 1,
(struct sockaddr*)&serv_addr, sizeof(serv_addr),
0, 0, 1, 0, 0));
EXPECT_NOTCAPABLE(sctp_sendmsg(cap_sock_rw, &byte, 1,
(struct sockaddr*)&serv_addr, sizeof(serv_addr),
0, 0, 1, 0, 0));
if (verbose) fprintf(stderr, " [%d]sctp_sendmsg(%02x)\n", getpid_(), byte);
EXPECT_OK(sctp_sendmsg(cap_sock_all, &byte, 1,
(struct sockaddr*)&serv_addr, sizeof(serv_addr),
0, 0, 1, 0, 0));
close(cap_sock_ro);
close(cap_sock_rw);
return cap_sock_all;
}
TEST(Sctp, Socket) {
int sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
EXPECT_OK(sock);
if (sock < 0) return;
cap_rights_init(&r_ro, CAP_READ);
cap_rights_init(&r_wo, CAP_WRITE);
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
cap_rights_init(&r_all_nopeel, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
cap_rights_clear(&r_all_nopeel, CAP_PEELOFF);
int cap_sock_wo = dup(sock);
EXPECT_OK(cap_sock_wo);
EXPECT_OK(cap_rights_limit(cap_sock_wo, &r_wo));
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
int cap_sock_all_nopeel = dup(sock);
EXPECT_OK(cap_sock_all_nopeel);
EXPECT_OK(cap_rights_limit(cap_sock_all_nopeel, &r_all_nopeel));
close(sock);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(addr);
// Can only bind the fully-capable socket.
EXPECT_NOTCAPABLE(bind(cap_sock_rw, (struct sockaddr *)&addr, len));
EXPECT_OK(bind(cap_sock_all, (struct sockaddr *)&addr, len));
EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr *)&addr, &len));
int port = ntohs(addr.sin_port);
// Now we know the port involved, fork off children to run clients.
pid_t child1 = fork();
if (child1 == 0) {
// Child process 1: wait for server setup
sleep(1);
// Send a message that triggers peeloff
int client_sock = SctpClient(port, DO_PEELOFF);
sleep(1);
close(client_sock);
exit(HasFailure());
}
pid_t child2 = fork();
if (child2 == 0) {
// Child process 2: wait for server setup
sleep(2);
// Send a message that triggers server exit
int client_sock = SctpClient(port, DO_TERM);
close(client_sock);
exit(HasFailure());
}
// Can only listen on the fully-capable socket.
EXPECT_NOTCAPABLE(listen(cap_sock_rw, 3));
EXPECT_OK(listen(cap_sock_all, 3));
// Can only do socket operations on the fully-capable socket.
len = sizeof(addr);
EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&addr, &len));
struct sctp_event_subscribe events;
memset(&events, 0, sizeof(events));
events.sctp_association_event = 1;
events.sctp_data_io_event = 1;
EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)));
len = sizeof(events);
EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, IPPROTO_SCTP, SCTP_EVENTS, &events, &len));
memset(&events, 0, sizeof(events));
events.sctp_association_event = 1;
events.sctp_data_io_event = 1;
EXPECT_OK(setsockopt(cap_sock_all, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)));
len = sizeof(events);
EXPECT_OK(getsockopt(cap_sock_all, IPPROTO_SCTP, SCTP_EVENTS, &events, &len));
len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&addr, &len));
EXPECT_EQ(AF_INET, addr.sin_family);
EXPECT_EQ(htons(port), addr.sin_port);
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
unsigned char buffer[1024];
struct sctp_sndrcvinfo sri;
memset(&sri, 0, sizeof(sri));
int flags = 0;
EXPECT_NOTCAPABLE(sctp_recvmsg(cap_sock_wo, buffer, sizeof(buffer),
(struct sockaddr*)&client_addr, &addr_len,
&sri, &flags));
while (true) {
retry:
memset(&sri, 0, sizeof(sri));
int len = sctp_recvmsg(cap_sock_rw, buffer, sizeof(buffer),
(struct sockaddr*)&client_addr, &addr_len,
&sri, &flags);
if (len < 0 && errno == EAGAIN) goto retry;
EXPECT_OK(len);
if (len > 0) {
if (verbose) fprintf(stderr, "[%d]sctp_recvmsg(%02x..)", getpid_(), (unsigned)buffer[0]);
if (buffer[0] == DO_PEELOFF) {
if (verbose) fprintf(stderr, "..peeling off association %08lx\n", (long)sri.sinfo_assoc_id);
// Peel off the association. Needs CAP_PEELOFF.
int rc1 = sctp_peeloff(cap_sock_all_nopeel, sri.sinfo_assoc_id);
EXPECT_NOTCAPABLE(rc1);
int rc2 = sctp_peeloff(cap_sock_all, sri.sinfo_assoc_id);
EXPECT_OK(rc2);
int peeled = std::max(rc1, rc2);
if (peeled > 0) {
#ifdef CAP_FROM_PEELOFF
// Peeled off FD should have same rights as original socket.
cap_rights_t rights;
EXPECT_OK(cap_rights_get(peeled, &rights));
EXPECT_RIGHTS_EQ(&r_all, &rights);
#endif
close(peeled);
}
} else if (buffer[0] == DO_TERM) {
if (verbose) fprintf(stderr, "..terminating server\n");
break;
}
} else if (len < 0) {
break;
}
}
// Wait for the children.
int status;
int rc;
EXPECT_EQ(child1, waitpid(child1, &status, 0));
rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
EXPECT_EQ(child2, waitpid(child2, &status, 0));
rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
close(cap_sock_wo);
close(cap_sock_rw);
close(cap_sock_all);
close(cap_sock_all_nopeel);
}
#endif

View File

@ -0,0 +1,142 @@
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
namespace {
int AddFDToSet(fd_set* fset, int fd, int maxfd) {
FD_SET(fd, fset);
if (fd > maxfd) maxfd = fd;
return maxfd;
}
int InitFDSet(fd_set* fset, int *fds, int fdcount) {
FD_ZERO(fset);
int maxfd = -1;
for (int ii = 0; ii < fdcount; ii++) {
maxfd = AddFDToSet(fset, fds[ii], maxfd);
}
return maxfd;
}
} // namespace
FORK_TEST_ON(Select, LotsOFileDescriptors, TmpFile("cap_select")) {
int fd = open(TmpFile("cap_select"), O_RDWR | O_CREAT, 0644);
EXPECT_OK(fd);
if (fd < 0) return;
// Create many POLL_EVENT capabilities.
const int kCapCount = 64;
int cap_fd[kCapCount];
cap_rights_t r_poll;
cap_rights_init(&r_poll, CAP_EVENT);
for (int ii = 0; ii < kCapCount; ii++) {
cap_fd[ii] = dup(fd);
EXPECT_OK(cap_fd[ii]);
EXPECT_OK(cap_rights_limit(cap_fd[ii], &r_poll));
}
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE, CAP_SEEK);
int cap_rw = dup(fd);
EXPECT_OK(cap_rw);
EXPECT_OK(cap_rights_limit(cap_rw, &r_rw));
EXPECT_OK(cap_enter()); // Enter capability mode
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100;
// Add normal file descriptor and all CAP_EVENT capabilities
fd_set rset;
fd_set wset;
int maxfd = InitFDSet(&rset, cap_fd, kCapCount);
maxfd = AddFDToSet(&rset, fd, maxfd);
InitFDSet(&wset, cap_fd, kCapCount);
AddFDToSet(&rset, fd, 0);
int ret = select(maxfd+1, &rset, &wset, NULL, &tv);
EXPECT_OK(ret);
// Now also include the capability with no CAP_EVENT.
InitFDSet(&rset, cap_fd, kCapCount);
AddFDToSet(&rset, fd, maxfd);
maxfd = AddFDToSet(&rset, cap_rw, maxfd);
InitFDSet(&wset, cap_fd, kCapCount);
AddFDToSet(&wset, fd, maxfd);
AddFDToSet(&wset, cap_rw, maxfd);
ret = select(maxfd+1, &rset, &wset, NULL, &tv);
EXPECT_NOTCAPABLE(ret);
#ifdef HAVE_PSELECT
// And again with pselect
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 100000;
maxfd = InitFDSet(&rset, cap_fd, kCapCount);
maxfd = AddFDToSet(&rset, fd, maxfd);
InitFDSet(&wset, cap_fd, kCapCount);
AddFDToSet(&rset, fd, 0);
ret = pselect(maxfd+1, &rset, &wset, NULL, &ts, NULL);
EXPECT_OK(ret);
InitFDSet(&rset, cap_fd, kCapCount);
AddFDToSet(&rset, fd, maxfd);
maxfd = AddFDToSet(&rset, cap_rw, maxfd);
InitFDSet(&wset, cap_fd, kCapCount);
AddFDToSet(&wset, fd, maxfd);
AddFDToSet(&wset, cap_rw, maxfd);
ret = pselect(maxfd+1, &rset, &wset, NULL, &ts, NULL);
EXPECT_NOTCAPABLE(ret);
#endif
}
FORK_TEST_ON(Poll, LotsOFileDescriptors, TmpFile("cap_poll")) {
int fd = open(TmpFile("cap_poll"), O_RDWR | O_CREAT, 0644);
EXPECT_OK(fd);
if (fd < 0) return;
// Create many POLL_EVENT capabilities.
const int kCapCount = 64;
struct pollfd cap_fd[kCapCount + 2];
cap_rights_t r_poll;
cap_rights_init(&r_poll, CAP_EVENT);
for (int ii = 0; ii < kCapCount; ii++) {
cap_fd[ii].fd = dup(fd);
EXPECT_OK(cap_fd[ii].fd);
EXPECT_OK(cap_rights_limit(cap_fd[ii].fd, &r_poll));
cap_fd[ii].events = POLLIN|POLLOUT;
}
cap_fd[kCapCount].fd = fd;
cap_fd[kCapCount].events = POLLIN|POLLOUT;
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE, CAP_SEEK);
int cap_rw = dup(fd);
EXPECT_OK(cap_rw);
EXPECT_OK(cap_rights_limit(cap_rw, &r_rw));
cap_fd[kCapCount + 1].fd = cap_rw;
cap_fd[kCapCount + 1].events = POLLIN|POLLOUT;
EXPECT_OK(cap_enter()); // Enter capability mode
EXPECT_OK(poll(cap_fd, kCapCount + 1, 10));
// Now also include the capability with no CAP_EVENT.
EXPECT_OK(poll(cap_fd, kCapCount + 2, 10));
EXPECT_NE(0, (cap_fd[kCapCount + 1].revents & POLLNVAL));
#ifdef HAVE_PPOLL
// And again with ppoll
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 100000;
EXPECT_OK(ppoll(cap_fd, kCapCount + 1, &ts, NULL));
// Now also include the capability with no CAP_EVENT.
EXPECT_OK(ppoll(cap_fd, kCapCount + 2, &ts, NULL));
EXPECT_NE(0, (cap_fd[kCapCount + 1].revents & POLLNVAL));
#endif
}

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
import sys
import re
_values = { # 2-tuple => name
(0x0000000000000000, 0x0000000000000100) : 'TTYHOOK',
(0x0000000000000040, 0x0000000000000000) : 'CREATE',
(0x0000000200000000, 0x0000000000000000) : 'GETSOCKNAME',
(0x0000000000000000, 0x0000000000100000) : 'KQUEUE_CHANGE',
(0x0000000000000000, 0x0000000000004000) : 'EXTATTR_LIST',
(0x0000000000000080, 0x0000000000000000) : 'FEXECVE',
(0x0000001000000000, 0x0000000000000000) : 'PEELOFF',
(0x0000000000000000, 0x0000000000800000) : 'NOTIFY',
(0x0000000000000000, 0x0000000000001000) : 'EXTATTR_DELETE',
(0x0000000040000000, 0x0000000000000000) : 'BIND',
(0x0000000000000000, 0x0000000000002000) : 'EXTATTR_GET',
(0x0000000000008000, 0x0000000000000000) : 'FCNTL',
(0x0000000000000000, 0x0000000000400000) : 'EPOLL_CTL',
(0x0000000000000004, 0x0000000000000000) : 'SEEK_TELL',
(0x000000000000000c, 0x0000000000000000) : 'SEEK',
(0x0000004000000000, 0x0000000000000000) : 'SHUTDOWN',
(0x0000000000000000, 0x0000000000000080) : 'IOCTL',
(0x0000000000000000, 0x0000000000000020) : 'EVENT',
(0x0000000400000000, 0x0000000000000000) : 'GETSOCKOPT',
(0x0000000080000000, 0x0000000000000000) : 'CONNECT',
(0x0000000000000000, 0x0000000000200000) : 'FSIGNAL',
(0x0000000000000000, 0x0000000000008000) : 'EXTATTR_SET',
(0x0000000000100000, 0x0000000000000000) : 'FSTATFS',
(0x0000000000040000, 0x0000000000000000) : 'FSCK',
(0x0000000000000000, 0x0000000000000800) : 'PDKILL_FREEBSD',
(0x0000000000000000, 0x0000000000000004) : 'SEM_GETVALUE',
(0x0000000000000000, 0x0000000000080000) : 'ACL_SET',
(0x0000000000200000, 0x0000000000000000) : 'FUTIMES',
(0x0000000000000200, 0x0000000000000000) : 'FTRUNCATE',
(0x0000000000000000, 0x0000000000000001) : 'MAC_GET',
(0x0000000000020000, 0x0000000000000000) : 'FPATHCONF',
(0x0000002000000000, 0x0000000000000000) : 'SETSOCKOPT',
(0x0000000000002000, 0x0000000000000000) : 'FCHMOD',
(0x0000000000000000, 0x0000000002000000) : 'PERFMON',
(0x0000000000004000, 0x0000000000000000) : 'FCHOWN',
(0x0000000000000400, 0x0000000000000000) : 'LOOKUP',
(0x0000000000400400, 0x0000000000000000) : 'LINKAT_TARGET',
(0x0000000000800400, 0x0000000000000000) : 'MKDIRAT',
(0x0000000001000400, 0x0000000000000000) : 'MKFIFOAT',
(0x0000000002000400, 0x0000000000000000) : 'MKNODAT',
(0x0000000004000400, 0x0000000000000000) : 'RENAMEAT_SOURCE',
(0x0000000008000400, 0x0000000000000000) : 'SYMLINKAT',
(0x0000000010000400, 0x0000000000000000) : 'UNLINKAT',
(0x0000008000000400, 0x0000000000000000) : 'BINDAT',
(0x0000010000000400, 0x0000000000000000) : 'CONNECTAT',
(0x0000020000000400, 0x0000000000000000) : 'LINKAT_SOURCE',
(0x0000040000000400, 0x0000000000000000) : 'RENAMEAT_TARGET',
(0x0000000000000010, 0x0000000000000000) : 'MMAP',
(0x000000000000003c, 0x0000000000000000) : 'MMAP_X',
(0x0000000000000000, 0x0000000001000000) : 'SETNS',
(0x0000000000080000, 0x0000000000000000) : 'FSTAT',
(0x0000000000000001, 0x0000000000000000) : 'READ',
(0x0000000000000000, 0x0000000000000008) : 'SEM_POST',
(0x0000000000000000, 0x0000000000020000) : 'ACL_DELETE',
(0x0000000000001000, 0x0000000000000000) : 'FCHFLAGS',
(0x0000000800000000, 0x0000000000000000) : 'LISTEN',
(0x0000000100000000, 0x0000000000000000) : 'GETPEERNAME',
(0x0000000000000100, 0x0000000000000000) : 'FSYNC',
(0x0000000000000000, 0x0000000004000000) : 'BPF',
(0x0000000020000000, 0x0000000000000000) : 'ACCEPT',
(0x0000000000000800, 0x0000000000000000) : 'FCHDIR',
(0x0000000000000002, 0x0000000000000000) : 'WRITE',
(0x0000000000000000, 0x0000000000000010) : 'SEM_WAIT',
(0x0000000000000000, 0x0000000000000040) : 'KQUEUE_EVENT',
(0x0000000000000000, 0x0000000000000400) : 'PDWAIT',
(0x0000000000000000, 0x0000000000040000) : 'ACL_GET',
(0x0000000000010000, 0x0000000000000000) : 'FLOCK',
(0x0000000000000000, 0x0000000000010000) : 'ACL_CHECK',
(0x0000000000000000, 0x0000000000000002) : 'MAC_SET',
(0x0000000000000000, 0x0000000000000200) : 'PDGETPID_FREEBSD',
}
def _map_fdinfo(line):
RIGHTS_RE = re.compile(r'(?P<prefix>.*)rights:(?P<ws>\s+)0x(?P<v0>[0-9a-fA-F]+)\s+0x(?P<v1>[0-9a-fA-F]+)$')
m = RIGHTS_RE.match(line)
if m:
val0 = long(m.group('v0'), 16)
val0 = (val0 & ~(0x0200000000000000L))
val1 = long(m.group('v1'), 16)
val1 = (val1 & ~(0x0400000000000000L))
rights = []
for (right, name) in _values.items():
if ((right[0] == 0 or (val0 & right[0])) and
(right[1] == 0 or (val1 & right[1]))):
rights.append(name)
return "%srights:%s%s" % (m.group('prefix'), m.group('ws'), '|'.join(rights))
else:
return line.rstrip()
if __name__ == "__main__":
infile = open(sys.argv[1], 'r') if len(sys.argv) > 1 else sys.stdin
for line in infile.readlines():
print _map_fdinfo(line)

View File

@ -0,0 +1,135 @@
/* Small standalone test program to check the existence of Capsicum syscalls */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include "capsicum.h"
#ifdef __linux__
// glibc on Linux caches getpid() return value.
int getpid_(void) { return syscall(__NR_getpid); }
#else
#define getpid_ getpid
#endif
static int seen_sigchld = 0;
static void handle_signal(int x) {
fprintf(stderr, "[%d] received SIGCHLD\n", getpid_());
seen_sigchld = 1;
}
int main(int argc, char *argv[]) {
signal(SIGCHLD, handle_signal);
int lifetime = 4; /* seconds */
if (1 < argc) {
lifetime = atoi(argv[1]);
}
/* cap_rights_limit() available? */
cap_rights_t r_rws;
cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK);
int cap_fd = dup(STDOUT_FILENO);
int rc = cap_rights_limit(cap_fd, &r_rws);
fprintf(stderr, "[%d] cap_fd=%d\n", getpid_(), cap_fd);
if (rc < 0) fprintf(stderr, "*** cap_rights_limit() failed: errno=%d %s\n", errno, strerror(errno));
/* cap_rights_get() available? */
cap_rights_t rights;
cap_rights_init(&rights, 0);
rc = cap_rights_get(cap_fd, &rights);
char buffer[256];
cap_rights_describe(&rights, buffer);
fprintf(stderr, "[%d] cap_rights_get(cap_fd=%d) rc=%d rights=%s\n", getpid_(), cap_fd, rc, buffer);
if (rc < 0) fprintf(stderr, "*** cap_rights_get() failed: errno=%d %s\n", errno, strerror(errno));
/* fstat() policed? */
struct stat buf;
rc = fstat(cap_fd, &buf);
fprintf(stderr, "[%d] fstat(cap_fd=%d) rc=%d errno=%d\n", getpid_(), cap_fd, rc, errno);
if (rc != -1) fprintf(stderr, "*** fstat() unexpectedly succeeded\n");
/* pdfork() available? */
int pd = -1;
rc = pdfork(&pd, 0);
if (rc < 0) fprintf(stderr, "*** pdfork() failed: errno=%d %s\n", errno, strerror(errno));
if (rc == 0) { /* child */
int count = 0;
while (count < 20) {
fprintf(stderr, " [%d] child alive, parent is ppid=%d\n", getpid_(), getppid());
sleep(1);
}
fprintf(stderr, " [%d] child exit(0)\n", getpid_());
exit(0);
}
fprintf(stderr, "[%d] pdfork() rc=%d pd=%d\n", getpid_(), rc, pd);
/* pdgetpid() available? */
pid_t actual_pid = rc;
pid_t got_pid = -1;
rc = pdgetpid(pd, &got_pid);
if (rc < 0) fprintf(stderr, "*** pdgetpid(pd=%d) failed: errno=%d %s\n", pd, errno, strerror(errno));
fprintf(stderr, "[%d] pdgetpid(pd=%d)=%d, pdfork returned %d\n", getpid_(), pd, got_pid, actual_pid);
sleep(lifetime);
/* pdkill() available? */
rc = pdkill(pd, SIGKILL);
fprintf(stderr, "[%d] pdkill(pd=%d, SIGKILL) -> rc=%d\n", getpid_(), pd, rc);
if (rc < 0) fprintf(stderr, "*** pdkill() failed: errno=%d %s\n", errno, strerror(errno));
usleep(50000); /* Allow time for death and signals */
/* Death of a pdforked child should be invisible */
if (seen_sigchld) fprintf(stderr, "*** SIGCHLD emitted\n");
int status;
rc = wait4(-1, &status, WNOHANG, NULL);
if (rc > 0) fprintf(stderr, "*** wait4(-1, ...) unexpectedly found child %d\n", rc);
fprintf(stderr, "[%d] forking off a child process to check cap_enter()\n", getpid_());
pid_t child = fork();
if (child == 0) { /* child */
/* cap_getmode() / cap_enter() available? */
unsigned int cap_mode = -1;
rc = cap_getmode(&cap_mode);
fprintf(stderr, " [%d] cap_getmode() -> rc=%d, cap_mode=%d\n", getpid_(), rc, cap_mode);
if (rc < 0) fprintf(stderr, "*** cap_getmode() failed: errno=%d %s\n", errno, strerror(errno));
rc = cap_enter();
fprintf(stderr, " [%d] cap_enter() -> rc=%d\n", getpid_(), rc);
if (rc < 0) fprintf(stderr, "*** cap_enter() failed: errno=%d %s\n", errno, strerror(errno));
rc = cap_getmode(&cap_mode);
fprintf(stderr, " [%d] cap_getmode() -> rc=%d, cap_mode=%d\n", getpid_(), rc, cap_mode);
if (rc < 0) fprintf(stderr, "*** cap_getmode() failed: errno=%d %s\n", errno, strerror(errno));
/* open disallowed? */
rc = open("/etc/passwd", O_RDONLY);
fprintf(stderr, " [%d] open('/etc/passwd') -> rc=%d, errno=%d\n", getpid_(), rc, errno);
if (rc != -1) fprintf(stderr, "*** open() unexpectedly succeeded\n");
#ifdef ECAPMODE
if (errno != ECAPMODE) fprintf(stderr, "*** open() failed with errno %d not ECAPMODE\n", errno);
#endif
exit(0);
}
rc = wait4(child, &status, 0, NULL);
fprintf(stderr, "[%d] child %d exited with status %x\n", getpid_(), child, status);
/* fexecve() available? */
char* argv_pass[] = {(char*)"/bin/ls", "-l", "smoketest", NULL};
char* null_envp[] = {NULL};
int ls_bin = open("/bin/ls", O_RDONLY);
fprintf(stderr, "[%d] about to fexecve('/bin/ls', '-l', 'smoketest')\n", getpid_());
rc = fexecve(ls_bin, argv_pass, null_envp);
/* should never reach here */
fprintf(stderr, "*** fexecve(fd=%d) failed: rc=%d errno=%d %s\n", ls_bin, rc, errno, strerror(errno));
return 0;
}

View File

@ -0,0 +1,340 @@
// Tests for socket functionality.
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include "capsicum.h"
#include "syscalls.h"
#include "capsicum-test.h"
TEST(Socket, UnixDomain) {
const char* socketName = TmpFile("capsicum-test.socket");
unlink(socketName);
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
cap_rights_t r_all;
cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
pid_t child = fork();
if (child == 0) {
// Child process: wait for server setup
sleep(1);
// Create sockets
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
EXPECT_OK(sock);
if (sock < 0) return;
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
EXPECT_OK(close(sock));
// Connect socket
struct sockaddr_un un;
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, socketName);
socklen_t len = sizeof(un);
EXPECT_NOTCAPABLE(connect_(cap_sock_rw, (struct sockaddr *)&un, len));
EXPECT_OK(connect_(cap_sock_all, (struct sockaddr *)&un, len));
exit(HasFailure());
}
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
EXPECT_OK(sock);
if (sock < 0) return;
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
EXPECT_OK(close(sock));
struct sockaddr_un un;
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, socketName);
socklen_t len = (sizeof(un) - sizeof(un.sun_path) + strlen(un.sun_path));
// Can only bind the fully-capable socket.
EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&un, len));
EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&un, len));
// Can only listen on the fully-capable socket.
EXPECT_NOTCAPABLE(listen(cap_sock_rw, 3));
EXPECT_OK(listen(cap_sock_all, 3));
// Can only do socket operations on the fully-capable socket.
len = sizeof(un);
EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&un, &len));
int value = 0;
EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_DEBUG, &value, sizeof(value)));
len = sizeof(value);
EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_DEBUG, &value, &len));
len = sizeof(un);
memset(&un, 0, sizeof(un));
EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&un, &len));
EXPECT_EQ(AF_UNIX, un.sun_family);
EXPECT_EQ(std::string(socketName), std::string(un.sun_path));
value = 0;
EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, sizeof(value)));
len = sizeof(value);
EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, &len));
// Accept the incoming connection
len = sizeof(un);
memset(&un, 0, sizeof(un));
EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&un, &len));
int conn_fd = accept(cap_sock_all, (struct sockaddr *)&un, &len);
EXPECT_OK(conn_fd);
#ifdef CAP_FROM_ACCEPT
// New connection should also be a capability.
cap_rights_t rights;
cap_rights_init(&rights, 0);
EXPECT_OK(cap_rights_get(conn_fd, &rights));
EXPECT_RIGHTS_IN(&rights, &r_all);
#endif
// Wait for the child.
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
close(conn_fd);
close(cap_sock_rw);
close(cap_sock_all);
unlink(socketName);
}
TEST(Socket, TCP) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
EXPECT_OK(sock);
if (sock < 0) return;
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
cap_rights_t r_all;
cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
close(sock);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(addr);
// Can only bind the fully-capable socket.
EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&addr, len));
EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&addr, len));
getsockname(cap_sock_all, (struct sockaddr *)&addr, &len);
int port = ntohs(addr.sin_port);
// Now we know the port involved, fork off a child.
pid_t child = fork();
if (child == 0) {
// Child process: wait for server setup
sleep(1);
// Create sockets
int sock = socket(AF_INET, SOCK_STREAM, 0);
EXPECT_OK(sock);
if (sock < 0) return;
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
close(sock);
// Connect socket
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port); // Pick unused port
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t len = sizeof(addr);
EXPECT_NOTCAPABLE(connect_(cap_sock_rw, (struct sockaddr *)&addr, len));
EXPECT_OK(connect_(cap_sock_all, (struct sockaddr *)&addr, len));
exit(HasFailure());
}
// Can only listen on the fully-capable socket.
EXPECT_NOTCAPABLE(listen(cap_sock_rw, 3));
EXPECT_OK(listen(cap_sock_all, 3));
// Can only do socket operations on the fully-capable socket.
len = sizeof(addr);
EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&addr, &len));
int value = 1;
EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)));
len = sizeof(value);
EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, &len));
len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&addr, &len));
EXPECT_EQ(AF_INET, addr.sin_family);
EXPECT_EQ(htons(port), addr.sin_port);
value = 0;
EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)));
len = sizeof(value);
EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len));
// Accept the incoming connection
len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&addr, &len));
int conn_fd = accept(cap_sock_all, (struct sockaddr *)&addr, &len);
EXPECT_OK(conn_fd);
#ifdef CAP_FROM_ACCEPT
// New connection should also be a capability.
cap_rights_t rights;
cap_rights_init(&rights, 0);
EXPECT_OK(cap_rights_get(conn_fd, &rights));
EXPECT_RIGHTS_IN(&rights, &r_all);
#endif
// Wait for the child.
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
close(conn_fd);
close(cap_sock_rw);
close(cap_sock_all);
}
TEST(Socket, UDP) {
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
EXPECT_OK(sock);
if (sock < 0) return;
cap_rights_t r_rw;
cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
cap_rights_t r_all;
cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
cap_rights_t r_connect;
cap_rights_init(&r_connect, CAP_READ, CAP_WRITE, CAP_CONNECT);
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_all = dup(sock);
EXPECT_OK(cap_sock_all);
EXPECT_OK(cap_rights_limit(cap_sock_all, &r_all));
close(sock);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(addr);
// Can only bind the fully-capable socket.
EXPECT_NOTCAPABLE(bind_(cap_sock_rw, (struct sockaddr *)&addr, len));
EXPECT_OK(bind_(cap_sock_all, (struct sockaddr *)&addr, len));
getsockname(cap_sock_all, (struct sockaddr *)&addr, &len);
int port = ntohs(addr.sin_port);
// Can only do socket operations on the fully-capable socket.
len = sizeof(addr);
EXPECT_NOTCAPABLE(getsockname(cap_sock_rw, (struct sockaddr*)&addr, &len));
int value = 1;
EXPECT_NOTCAPABLE(setsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)));
len = sizeof(value);
EXPECT_NOTCAPABLE(getsockopt(cap_sock_rw, SOL_SOCKET, SO_REUSEPORT, &value, &len));
len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
EXPECT_OK(getsockname(cap_sock_all, (struct sockaddr*)&addr, &len));
EXPECT_EQ(AF_INET, addr.sin_family);
EXPECT_EQ(htons(port), addr.sin_port);
value = 1;
EXPECT_OK(setsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)));
len = sizeof(value);
EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len));
pid_t child = fork();
if (child == 0) {
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
EXPECT_OK(sock);
int cap_sock_rw = dup(sock);
EXPECT_OK(cap_sock_rw);
EXPECT_OK(cap_rights_limit(cap_sock_rw, &r_rw));
int cap_sock_connect = dup(sock);
EXPECT_OK(cap_sock_connect);
EXPECT_OK(cap_rights_limit(cap_sock_connect, &r_connect));
close(sock);
// Can only sendmsg(2) to an address over a socket with CAP_CONNECT.
unsigned char buffer[256];
struct iovec iov;
memset(&iov, 0, sizeof(iov));
iov.iov_base = buffer;
iov.iov_len = sizeof(buffer);
struct msghdr mh;
memset(&mh, 0, sizeof(mh));
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
mh.msg_name = &addr;
mh.msg_namelen = sizeof(addr);
EXPECT_NOTCAPABLE(sendmsg(cap_sock_rw, &mh, 0));
EXPECT_OK(sendmsg(cap_sock_connect, &mh, 0));
#ifdef HAVE_SEND_RECV_MMSG
struct mmsghdr mv;
memset(&mv, 0, sizeof(mv));
memcpy(&mv.msg_hdr, &mh, sizeof(struct msghdr));
EXPECT_NOTCAPABLE(sendmmsg(cap_sock_rw, &mv, 1, 0));
EXPECT_OK(sendmmsg(cap_sock_connect, &mv, 1, 0));
#endif
close(cap_sock_rw);
close(cap_sock_connect);
exit(HasFailure());
}
// Wait for the child.
int status;
EXPECT_EQ(child, waitpid(child, &status, 0));
int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
EXPECT_EQ(0, rc);
close(cap_sock_rw);
close(cap_sock_all);
}

View File

@ -0,0 +1,259 @@
/*
* Minimal portability layer for system call differences between
* Capsicum OSes.
*/
#ifndef __SYSCALLS_H__
#define __SYSCALLS_H__
/************************************************************
* FreeBSD
************************************************************/
#ifdef __FreeBSD__
/* Map umount2 (Linux) syscall to unmount (FreeBSD) syscall */
#define umount2(T, F) unmount(T, F)
/* Map sighandler_y (Linux) to sig_t (FreeBSD) */
#define sighandler_t sig_t
/* profil(2) has a first argument of char* */
#define profil_arg1_t char
/* FreeBSD has getdents(2) available */
#include <sys/types.h>
#include <dirent.h>
inline int getdents_(unsigned int fd, void *dirp, unsigned int count) {
return getdents(fd, (char*)dirp, count);
}
#include <sys/mman.h>
inline int mincore_(void *addr, size_t length, unsigned char *vec) {
return mincore(addr, length, (char*)vec);
}
#define getpid_ getpid
/* Map Linux-style sendfile to FreeBSD sendfile */
#include <sys/socket.h>
#include <sys/uio.h>
inline ssize_t sendfile_(int out_fd, int in_fd, off_t *offset, size_t count) {
return sendfile(in_fd, out_fd, *offset, count, NULL, offset, 0);
}
/* A sample mount(2) call */
#include <sys/param.h>
#include <sys/mount.h>
inline int bogus_mount_() {
return mount("procfs", "/not_mounted", 0, NULL);
}
/* Mappings for extended attribute functions */
#include <sys/extattr.h>
inline ssize_t flistxattr_(int fd, char *list, size_t size) {
return extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, list, size);
}
inline ssize_t fgetxattr_(int fd, const char *name, void *value, size_t size) {
return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size);
}
inline int fsetxattr_(int fd, const char *name, const void *value, size_t size, int) {
return extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size);
}
inline int fremovexattr_(int fd, const char *name) {
return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, name);
}
/* mq_* functions are wrappers in FreeBSD so go through to underlying syscalls */
#include <sys/syscall.h>
extern "C" {
extern int __sys_kmq_notify(int, const struct sigevent *);
extern int __sys_kmq_open(const char *, int, mode_t, const struct mq_attr *);
extern int __sys_kmq_setattr(int, const struct mq_attr *__restrict, struct mq_attr *__restrict);
extern ssize_t __sys_kmq_timedreceive(int, char *__restrict, size_t,
unsigned *__restrict, const struct timespec *__restrict);
extern int __sys_kmq_timedsend(int, const char *, size_t, unsigned,
const struct timespec *);
extern int __sys_kmq_unlink(const char *);
}
#define mq_notify_ __sys_kmq_notify
#define mq_open_ __sys_kmq_open
#define mq_setattr_ __sys_kmq_setattr
#define mq_getattr_(A, B) __sys_kmq_setattr(A, NULL, B)
#define mq_timedreceive_ __sys_kmq_timedreceive
#define mq_timedsend_ __sys_kmq_timedsend
#define mq_unlink_ __sys_kmq_unlink
#define mq_close_ close
#include <sys/ptrace.h>
inline long ptrace_(int request, pid_t pid, void *addr, void *data) {
return ptrace(request, pid, (caddr_t)addr, static_cast<int>((long)data));
}
#define PTRACE_PEEKDATA_ PT_READ_D
#define getegid_ getegid
#define getgid_ getgid
#define geteuid_ geteuid
#define getuid_ getuid
#define getgroups_ getgroups
#define getrlimit_ getrlimit
#define bind_ bind
#define connect_ connect
/* Features available */
#if __FreeBSD_version >= 1000000
#define HAVE_CHFLAGSAT
#define HAVE_BINDAT
#define HAVE_CONNECTAT
#endif
#define HAVE_CHFLAGS
#define HAVE_GETFSSTAT
#define HAVE_REVOKE
#define HAVE_GETLOGIN
#define HAVE_MKFIFOAT
#define HAVE_SYSARCH
#include <machine/sysarch.h>
#define HAVE_STAT_BIRTHTIME
#define HAVE_SYSCTL
#define HAVE_FPATHCONF
#define HAVE_F_DUP2FD
#define HAVE_PSELECT
#define HAVE_SCTP
/* FreeBSD only allows root to call mlock[all]/munlock[all] */
#define MLOCK_REQUIRES_ROOT 1
/* FreeBSD effectively only allows root to call sched_setscheduler */
#define SCHED_SETSCHEDULER_REQUIRES_ROOT 1
#endif /* FreeBSD */
/************************************************************
* Linux
************************************************************/
#ifdef __linux__
#include <fcntl.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <sys/statfs.h>
#include <sys/xattr.h>
#include <sys/mount.h>
#include <linux/net.h>
/* profil(2) has a first argument of unsigned short* */
#define profil_arg1_t unsigned short
static inline int getdents_(unsigned int fd, void *dirp, unsigned int count) {
return syscall(__NR_getdents, fd, dirp, count);
}
/* A sample mount(2) call */
static inline int bogus_mount_() {
return mount("/dev/bogus", "/bogus", "debugfs", MS_RDONLY, "");
}
/* libc's getpid() wrapper caches the pid value, and doesn't invalidate
* the cached value on pdfork(), so directly syscall. */
static inline pid_t getpid_() {
return syscall(__NR_getpid);
}
static inline int execveat(int fd, const char *path,
char *const argv[], char *const envp[], int flags) {
return syscall(__NR_execveat, fd, path, argv, envp, flags);
}
/*
* Linux glibc includes an fexecve() function, implemented via the /proc
* filesystem. Bypass this and go directly to the execveat(2) syscall.
*/
static inline int fexecve_(int fd, char *const argv[], char *const envp[]) {
return execveat(fd, "", argv, envp, AT_EMPTY_PATH);
}
/*
* Linux glibc attempts to be clever and intercepts various uid/gid functions.
* Bypass by calling the syscalls directly.
*/
static inline gid_t getegid_(void) { return syscall(__NR_getegid); }
static inline gid_t getgid_(void) { return syscall(__NR_getgid); }
static inline uid_t geteuid_(void) { return syscall(__NR_geteuid); }
static inline uid_t getuid_(void) { return syscall(__NR_getuid); }
static inline int getgroups_(int size, gid_t list[]) { return syscall(__NR_getgroups, size, list); }
static inline int getrlimit_(int resource, struct rlimit *rlim) {
return syscall(__NR_getrlimit, resource, rlim);
}
/*
* Linux glibc for i386 consumes the errno returned from the raw socketcall(2) operation,
* so use the raw syscall for those operations that are disallowed in capability mode.
*/
#ifdef __NR_bind
#define bind_ bind
#else
static inline int bind_(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
unsigned long args[3] = {(unsigned long)sockfd, (unsigned long)(intptr_t)addr, (unsigned long)addrlen};
return syscall(__NR_socketcall, SYS_BIND, args);
}
#endif
#ifdef __NR_connect
#define connect_ connect
#else
static inline int connect_(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
unsigned long args[3] = {(unsigned long)sockfd, (unsigned long)(intptr_t)addr, (unsigned long)addrlen};
return syscall(__NR_socketcall, SYS_CONNECT, args);
}
#endif
#define mincore_ mincore
#define sendfile_ sendfile
#define flistxattr_ flistxattr
#define fgetxattr_ fgetxattr
#define fsetxattr_ fsetxattr
#define fremovexattr_ fremovexattr
#define mq_notify_ mq_notify
#define mq_open_ mq_open
#define mq_setattr_ mq_setattr
#define mq_getattr_ mq_getattr
#define mq_timedreceive_ mq_timedreceive
#define mq_timedsend_ mq_timedsend
#define mq_unlink_ mq_unlink
#define mq_close_ mq_close
#define ptrace_ ptrace
#define PTRACE_PEEKDATA_ PTRACE_PEEKDATA
/* Features available */
#define HAVE_DUP3
#define HAVE_PIPE2
#include <sys/fsuid.h> /* for setfsgid()/setfsuid() */
#define HAVE_SETFSUID
#define HAVE_SETFSGID
#define HAVE_READAHEAD
#define HAVE_SEND_RECV_MMSG
#define HAVE_SYNCFS
#define HAVE_SYNC_FILE_RANGE
#include <sys/uio.h> /* for vmsplice */
#define HAVE_TEE
#define HAVE_SPLICE
#define HAVE_VMSPLICE
#define HAVE_PSELECT
#define HAVE_PPOLL
#define HAVE_EXECVEAT
#define HAVE_SYSCALL
#define HAVE_MKNOD_REG
#define HAVE_MKNOD_SOCKET
/*
* O_BENEATH is arch-specific, via <asm/fcntl.h>; however we cannot include both that file
* and the normal <fcntl.h> as they have some clashing definitions. Bypass by directly
* defining O_BENEATH, using the current proposed x86 value. (This will therefore not
* work for non-x86, and may need changing in future if a different value gets merged.)
*/
#ifndef O_BENEATH
#define O_BENEATH 040000000 /* no / or .. in openat path */
#endif
/* Linux allows anyone to call mlock[all]/munlock[all] */
#define MLOCK_REQUIRES_ROOT 0
/* Linux allows anyone to call sched_setscheduler */
#define SCHED_SETSCHEDULER_REQUIRES_ROOT 1
#endif /* Linux */
#endif /*__SYSCALLS_H__*/

View File

@ -0,0 +1,15 @@
#include "capsicum.h"
#include "capsicum-test.h"
#ifdef HAVE_SYSCTL
#include <sys/sysctl.h>
// Certain sysctls are permitted in capability mode, but most are not. Test
// for the ones that should be, and try one or two that shouldn't.
TEST(Sysctl, Capability) {
int oid[2] = {CTL_KERN, KERN_OSRELDATE};
int ii;
size_t len = sizeof(ii);
EXPECT_OK(sysctl(oid, 2, &ii, &len, NULL, 0));
}
#endif

View File

@ -0,0 +1,42 @@
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef __FreeBSD__
#include <sys/procdesc.h>
#endif
#ifdef __linux__
#include <sys/syscall.h>
int pdfork(int *fd, int flags) {
return syscall(__NR_pdfork, fd, flags);
}
#endif
int main() {
int procfd;
int rc = pdfork(&procfd, 0);
if (rc < 0) {
fprintf(stderr, "pdfork() failed rc=%d errno=%d %s\n", rc, errno, strerror(errno));
exit(1);
}
if (rc == 0) { // Child process
sleep(1);
exit(123);
}
fprintf(stderr, "pdfork()ed child pid=%ld procfd=%d\n", (long)rc, procfd);
sleep(2); // Allow child to complete
pid_t child = waitpid(-1, &rc, WNOHANG);
if (child == 0) {
fprintf(stderr, "waitpid(): no completed child found\n");
} else if (child < 0) {
fprintf(stderr, "waitpid(): failed errno=%d %s\n", errno, strerror(errno));
} else {
fprintf(stderr, "waitpid(): found completed child %ld\n", (long)child);
}
return 0;
}

View File

@ -25,8 +25,10 @@
*/
#include <sys/types.h>
#include <sys/capsicum.h>
#include <sys/stat.h>
#include <capsicum_helpers.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
@ -44,6 +46,9 @@
#include <libelftc.h>
#include <gelf.h>
#include <libcasper.h>
#include <casper/cap_fileargs.h>
#include "_elftc.h"
ELFTC_VCSID("$Id: strings.c 3648 2018-11-22 23:26:43Z emaste $");
@ -85,7 +90,7 @@ static struct option strings_longopts[] = {
};
int getcharacter(FILE *, long *);
int handle_file(const char *);
int handle_file(fileargs_t *fa, const char *);
int handle_elf(const char *, FILE *);
int handle_binary(const char *, FILE *, size_t);
int find_strings(const char *, FILE *, off_t, off_t);
@ -99,6 +104,8 @@ void usage(void);
int
main(int argc, char **argv)
{
fileargs_t *fa;
cap_rights_t rights;
int ch, rc;
rc = 0;
@ -187,27 +194,41 @@ main(int argc, char **argv)
argc -= optind;
argv += optind;
cap_rights_init(&rights, CAP_READ, CAP_SEEK, CAP_FSTAT, CAP_FCNTL);
fa = fileargs_init(argc, argv, O_RDONLY, 0, &rights);
if (fa == NULL)
err(1, "Unable to initialize casper fileargs");
caph_cache_catpages();
if (caph_limit_stdio() < 0 && caph_enter_casper() < 0) {
fileargs_free(fa);
err(1, "Unable to enter capability mode");
}
if (min_len == 0)
min_len = 4;
if (*argv == NULL)
rc = find_strings("{standard input}", stdin, 0, 0);
else while (*argv != NULL) {
if (handle_file(*argv) != 0)
if (handle_file(fa, *argv) != 0)
rc = 1;
argv++;
}
fileargs_free(fa);
return (rc);
}
int
handle_file(const char *name)
handle_file(fileargs_t *fa, const char *name)
{
FILE *pfile;
int rt;
if (name == NULL)
return (1);
pfile = fopen(name, "rb");
pfile = fileargs_fopen(fa, name, "rb");
if (pfile == NULL) {
warnx("'%s': %s", name, strerror(errno));
return (1);

View File

@ -217,6 +217,7 @@ if (gtest_build_tests)
test/gtest-typed-test2_test.cc)
cxx_test(gtest_unittest gtest_main)
cxx_test(gtest-unittest-api_test gtest)
cxx_test(gtest_skip_in_environment_setup_test gtest_main)
cxx_test(gtest_skip_test gtest_main)
############################################################

View File

@ -290,6 +290,12 @@ test_gtest_all_test_SOURCES = test/gtest_all_test.cc
test_gtest_all_test_LDADD = lib/libgtest_main.la \
lib/libgtest.la
TESTS += test/gtest_skip_in_environment_setup_test
check_PROGRAMS += test/gtest_skip_in_environment_setup_test
test_gtest_skip_in_environment_setup_test_SOURCES = test/gtest_skip_in_environment_setup_test.cc
test_gtest_skip_in_environment_setup_test_LDADD= lib/libgtest_main.la \
lib/libgtest.la
# Tests that fused gtest files compile and work.
FUSED_GTEST_SRC = \
fused-src/gtest/gtest-all.cc \

View File

@ -1289,8 +1289,10 @@ Environment* AddGlobalTestEnvironment(Environment* env);
```
Now, when `RUN_ALL_TESTS()` is called, it first calls the `SetUp()` method of
the environment object, then runs the tests if there was no fatal failures, and
finally calls `TearDown()` of the environment object.
each environment object, then runs the tests if none of the environments
reported fatal failures and `GTEST_SKIP()` was not called. `RUN_ALL_TESTS()`
always calls `TearDown()` with each environment object, regardless of whether
or not the tests were run.
It's OK to register multiple environment objects. In this case, their `SetUp()`
will be called in the order they are registered, and their `TearDown()` will be

View File

@ -5243,9 +5243,23 @@ bool UnitTestImpl::RunAllTests() {
ForEach(environments_, SetUpEnvironment);
repeater->OnEnvironmentsSetUpEnd(*parent_);
// Runs the tests only if there was no fatal failure during global
// set-up.
if (!Test::HasFatalFailure()) {
// Runs the tests only if there was no fatal failure or skip triggered
// during global set-up.
if (Test::IsSkipped()) {
// Emit diagnostics when global set-up calls skip, as it will not be
// emitted by default.
TestResult& test_result =
*internal::GetUnitTestImpl()->current_test_result();
for (int j = 0; j < test_result.total_part_count(); ++j) {
const TestPartResult& test_part_result =
test_result.GetTestPartResult(j);
if (test_part_result.type() == TestPartResult::kSkip) {
const std::string& result = test_part_result.message();
printf("%s\n", result.c_str());
}
}
fflush(stdout);
} else if (!Test::HasFatalFailure()) {
for (int test_index = 0; test_index < total_test_case_count();
test_index++) {
GetMutableTestCase(test_index)->Run();

View File

@ -311,6 +311,13 @@ cc_binary(
deps = ["//:gtest"],
)
cc_test(
name = "gtest_skip_in_environment_setup_test",
size = "small",
srcs = ["gtest_skip_in_environment_setup_test.cc"],
deps = ["//:gtest_main"],
)
py_test(
name = "googletest-list-tests-unittest",
size = "small",

View File

@ -12,7 +12,7 @@
#
AC_PREREQ(2.2)
AC_INIT([libxo], [0.9.0], [phil@juniper.net])
AC_INIT([libxo], [1.0.2], [phil@juniper.net])
AM_INIT_AUTOMAKE([-Wall -Werror foreign -Wno-portability])
# Support silent build rules. Requires at least automake-1.11.

View File

@ -400,28 +400,28 @@ string, since an inappropriate cast can ruin your day. The vap
argument to `xo_emit_hv` points to a variable argument list that can
be used to retrieve arguments via `va_arg`.
.. c:function:: int xo_emit (const char *fmt, ...)
.. c:function:: xo_ssize_t xo_emit (const char *fmt, ...)
:param fmt: The format string, followed by zero or more arguments
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
.. c:function:: int xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
.. c:function:: xo_ssize_t xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
:param xop: Handle for modify (or NULL for default handle)
:type xop: xo_handle_t \*
:param fmt: The format string, followed by zero or more arguments
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
.. c:function:: int xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
.. c:function:: xo_ssize_t xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
:param xop: Handle for modify (or NULL for default handle)
:type xop: xo_handle_t \*
:param fmt: The format string
:param va_list vap: A set of variadic arguments
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
.. index:: xo_emit_field
@ -434,7 +434,7 @@ scenario where one would otherwise need to compose a format
descriptors using `snprintf`. The individual parts of the format
descriptor are passed in distinctly.
.. c:function:: int xo_emit_field (const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...)
.. c:function:: xo_ssize_t xo_emit_field (const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...)
:param rolmod: A comma-separated list of field roles and field modifiers
:type rolmod: const char *
@ -445,7 +445,7 @@ descriptor are passed in distinctly.
:param efmt: Encoding format string, followed by additional arguments
:type efmt: const char *
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
::
@ -453,7 +453,7 @@ descriptor are passed in distinctly.
xo_emit_field("T", "Host name is ", NULL, NULL);
xo_emit_field("V", "host-name", NULL, NULL, host-name);
.. c:function:: int xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...)
.. c:function:: xo_ssize_t xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...)
:param xop: Handle for modify (or NULL for default handle)
:type xop: xo_handle_t \*
@ -466,9 +466,9 @@ descriptor are passed in distinctly.
:param efmt: Encoding format string, followed by additional arguments
:type efmt: const char *
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
.. c:function:: int xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, va_list vap)
.. c:function:: xo_ssize_t xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, va_list vap)
:param xop: Handle for modify (or NULL for default handle)
:type xop: xo_handle_t \*
@ -482,7 +482,7 @@ descriptor are passed in distinctly.
:type efmt: const char *
:param va_list vap: A set of variadic arguments
:returns: If XOF_COLUMNS is set, the number of columns used; otherwise the number of bytes emitted
:rtype: int
:rtype: xo_ssize_t
.. index:: xo_attr
.. _xo_attr:
@ -505,14 +505,14 @@ Since attributes are only emitted in XML, their use should be limited
to meta-data and additional or redundant representations of data
already emitted in other form.
.. c:function:: int xo_attr (const char *name, const char *fmt, ...)
.. c:function:: xo_ssize_t xo_attr (const char *name, const char *fmt, ...)
:param name: Attribute name
:type name: const char *
:param fmt: Attribute value, as variadic arguments
:type fmt: const char *
:returns: -1 for error, or the number of bytes in the formatted attribute value
:rtype: int
:rtype: xo_ssize_t
::
@ -525,7 +525,7 @@ already emitted in other form.
<login-time seconds="1408336270">00:14</login-time>
.. c:function:: int xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
.. c:function:: xo_ssize_t xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
:param xop: Handle for modify (or NULL for default handle)
:type xop: xo_handle_t \*
@ -533,7 +533,7 @@ already emitted in other form.
The `xo_attr_h` function follows the conventions of `xo_attr` but
adds an explicit libxo handle.
.. c:function:: int xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
.. c:function:: xo_ssize_t xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
The `xo_attr_h` function follows the conventions of `xo_attr_h`
but replaced the variadic list with a variadic pointer.

View File

@ -515,7 +515,7 @@ li.indline1 {
}
@top-right {
content: "May 2018";
content: "April 2019";
}
@top-center {
@ -22011,7 +22011,7 @@ jQuery(function ($) {
</tr>
<tr>
<td class="header left"></td>
<td class="header right">May 21, 2018</td>
<td class="header right">April 2, 2019</td>
</tr>
</table></div>
<p id="title" class="title">libxo: The Easy Way to Generate text, XML, JSON, and HTML output<br><span class="filename">libxo-manual</span></p>

View File

@ -75,7 +75,7 @@ prepend data to the XPath values used for HTML output style::
EXAMPLE;
#!/bin/sh
xo --open top/data
xo --depth 2 '{tag}' value
xo --depth 2 '{:tag}' value
xo --close top/data
XML:
<top>
@ -90,6 +90,84 @@ prepend data to the XPath values used for HTML output style::
}
}
When making partial lines of output (where the format string does not
include a newline), use the `--continuation` option to let secondary
invocations know they are adding data to an existing line.
When emitting a series of objects, use the `--not-first` option to
ensure that any details from the previous object (e.g. commas in JSON)
are handled correctly.
Use the `--top-wrap` option to ensure any top-level object details are
handled correctly, e.g. wrap the entire output in a top-level set of
braces for JSON output.
EXAMPLE;
#!/bin/sh
xo --top-wrap --open top/data
xo --depth 2 'First {:tag} ' value1
xo --depth 2 --continuation 'and then {:tag}\n' value2
xo --top-wrap --close top/data
TEXT:
First value1 and then value2
HTML:
<div class="line">
<div class="text">First </div>
<div class="data" data-tag="tag">value1</div>
<div class="text"> </div>
<div class="text">and then </div>
<div class="data" data-tag="tag">value2</div>
</div>
XML:
<top>
<data>
<tag>value1</tag>
<tag>value2</tag>
</data>
</top>
JSON:
{
"top": {
"data": {
"tag": "value1",
"tag": "value2"
}
}
}
Lists and Instances
-------------------
A "*list*" is set of one or more instances that appear under the same
parent. The instances contain details about a specific object. One
can think of instances as objects or records. A call is needed to
open and close the list, while a distinct call is needed to open and
close each instance of the list.
Use the `--open-list` and `--open-instances` to open lists and
instances. Use the `--close-list` and `--close-instances` to close
them. Each of these options take a `name` parameter, providing the
name of the list and instance.
In the following example, a list named "machine" is created with three
instances:
opts="--json"
xo $opts --open-list machine
NF=
for name in red green blue; do
xo $opts --depth 1 $NF --open-instance machine
xo $opts --depth 2 "Machine {k:name} has {:memory}\n" $name 55
xo $opts --depth 1 --close-instance machine
NF=--not-first
done
xo $opts $NF --close-list machine
The normal `libxo` functions use a state machine to help these
transitions, but since each `xo` command is invoked independent of the
previous calls, the state must be passed in explicitly via these
command line options.
Command Line Options
--------------------
@ -97,15 +175,23 @@ Command Line Options
Usage: xo [options] format [fields]
--close <path> Close tags for the given path
--close-instance <name> Close an open instance name
--close-list <name> Close an open list name
--continuation OR -C Output belongs on same line as previous output
--depth <num> Set the depth for pretty printing
--help Display this help text
--html OR -H Generate HTML output
--json OR -J Generate JSON output
--leading-xpath <path> Add a prefix to generated XPaths (HTML)
--not-first Indicate this object is not the first (JSON)
--open <path> Open tags for the given path
--open-instance <name> Open an instance given by name
--open-list <name> Open a list given by name
--option <opts> -or -O <opts> Give formatting options
--pretty OR -p Make 'pretty' output (add indent, newlines)
--style <style> Generate given style (xml, json, text, html)
--text OR -T Generate text output (the default style)
--top-wrap Generate a top-level object wrapper (JSON)
--version Display version information
--warn OR -W Display warnings in text on stderr
--warn-xml Display warnings in xml on stdout

View File

@ -35,6 +35,7 @@ libxoinc_HEADERS = \
noinst_HEADERS = \
xo_buf.h \
xo_explicit.h \
xo_humanize.h \
xo_wcwidth.h

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014-2015, Juniper Networks, Inc.
* Copyright (c) 2014-2018, Juniper Networks, Inc.
* All rights reserved.
* This SOFTWARE is licensed under the LICENSE provided in the
* ../Copyright file. By downloading, installing, copying, or otherwise
@ -47,6 +47,7 @@
#include "xo.h"
#include "xo_encoder.h"
#include "xo_buf.h"
#include "xo_explicit.h"
/*
* We ask wcwidth() to do an impossible job, really. It's supposed to
@ -158,40 +159,9 @@ typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
(XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
/*
* A word about states: We use a finite state machine (FMS) approach
* to help remove fragility from the caller's code. Instead of
* requiring a specific order of calls, we'll allow the caller more
* flexibility and make the library responsible for recovering from
* missed steps. The goal is that the library should not be capable
* of emitting invalid xml or json, but the developer shouldn't need
* to know or understand all the details about these encodings.
*
* You can think of states as either states or events, since they
* function rather like both. None of the XO_CLOSE_* events will
* persist as states, since the matching stack frame will be popped.
* Same is true of XSS_EMIT, which is an event that asks us to
* prep for emitting output fields.
* Turn the transition between two states into a number suitable for
* a "switch" statement.
*/
/* Stack frame states */
typedef unsigned xo_state_t;
#define XSS_INIT 0 /* Initial stack state */
#define XSS_OPEN_CONTAINER 1
#define XSS_CLOSE_CONTAINER 2
#define XSS_OPEN_LIST 3
#define XSS_CLOSE_LIST 4
#define XSS_OPEN_INSTANCE 5
#define XSS_CLOSE_INSTANCE 6
#define XSS_OPEN_LEAF_LIST 7
#define XSS_CLOSE_LEAF_LIST 8
#define XSS_DISCARDING 9 /* Discarding data until recovered */
#define XSS_MARKER 10 /* xo_open_marker's marker */
#define XSS_EMIT 11 /* xo_emit has a leaf field */
#define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */
#define XSS_FINISH 13 /* xo_finish was called */
#define XSS_MAX 13
#define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
/*
@ -288,8 +258,8 @@ struct xo_handle_s {
ssize_t xo_units_offset; /* Start of units insertion point */
ssize_t xo_columns; /* Columns emitted during this xo_emit call */
#ifndef LIBXO_TEXT_ONLY
uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
xo_color_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
xo_color_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
#endif /* LIBXO_TEXT_ONLY */
xo_colors_t xo_colors; /* Current color and effect values */
xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */
@ -321,6 +291,7 @@ struct xo_handle_s {
#define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
#define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
#define XOIF_MADE_OUTPUT XOF_BIT(6) /* Have already made output */
/* Flags for formatting functions */
typedef unsigned long xo_xff_flags_t;
@ -468,7 +439,7 @@ static void
xo_failure (xo_handle_t *xop, const char *fmt, ...);
static ssize_t
xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
xo_state_t new_state);
static int
@ -505,6 +476,20 @@ xo_style (xo_handle_t *xop UNUSED)
#endif /* LIBXO_TEXT_ONLY */
}
/*
* Allow the compiler to optimize out non-text-only code while
* still compiling it.
*/
static inline int
xo_text_only (void)
{
#ifdef LIBXO_TEXT_ONLY
return TRUE;
#else /* LIBXO_TEXT_ONLY */
return FALSE;
#endif /* LIBXO_TEXT_ONLY */
}
/*
* Callback to write data to a FILE pointer
*/
@ -606,6 +591,28 @@ xo_no_setlocale (void)
xo_locale_inited = 1; /* Skip initialization */
}
/*
* For XML, the first character of a tag cannot be numeric, but people
* will likely not notice. So we people-proof them by forcing a leading
* underscore if they use invalid tags. Note that this doesn't cover
* all broken tags, just this fairly specific case.
*/
static const char *
xo_xml_leader_len (xo_handle_t *xop, const char *name, xo_ssize_t nlen)
{
if (isalpha(name[0]) || name[0] == '_')
return "";
xo_failure(xop, "invalid XML tag name: '%.*s'", nlen, name);
return "_";
}
static const char *
xo_xml_leader (xo_handle_t *xop, const char *name)
{
return xo_xml_leader_len(xop, name, strlen(name));
}
/*
* We need to decide if stdout is line buffered (_IOLBF). Lacking a
* standard way to decide this (e.g. getlinebuf()), we have configure
@ -2194,9 +2201,8 @@ xo_set_style_name (xo_handle_t *xop, const char *name)
static void
xo_set_color_map (xo_handle_t *xop, char *value)
{
#ifdef LIBXO_TEXT_ONLY
return;
#endif /* LIBXO_TEXT_ONLY */
if (xo_text_only())
return;
char *cp, *ep, *vp, *np;
ssize_t len = value ? strlen(value) + 1 : 0;
@ -2214,8 +2220,11 @@ xo_set_color_map (xo_handle_t *xop, char *value)
fg = *cp ? xo_color_find(cp) : -1;
bg = (vp && *vp) ? xo_color_find(vp) : -1;
#ifndef LIBXO_TEXT_ONLY
xop->xo_color_map_fg[num] = (fg < 0) ? num : fg;
xop->xo_color_map_bg[num] = (bg < 0) ? num : bg;
#endif /* LIBXO_TEXT_ONLY */
if (++num > XO_NUM_COLORS)
break;
}
@ -2226,9 +2235,11 @@ xo_set_color_map (xo_handle_t *xop, char *value)
else
XOF_CLEAR(xop, XOF_COLOR_MAP);
#ifndef LIBXO_TEXT_ONLY
/* Fill in the rest of the colors with the defaults */
for ( ; num < XO_NUM_COLORS; num++)
xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num;
#endif /* LIBXO_TEXT_ONLY */
}
static int
@ -2600,6 +2611,12 @@ xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
static char div_open[] = "<div class=\"line\">";
static char div_open_blank[] = "<div class=\"blank-line\">";
if (XOF_ISSET(xop, XOF_CONTINUATION)) {
XOF_CLEAR(xop, XOF_CONTINUATION);
XOIF_SET(xop, XOIF_DIV_OPEN);
return;
}
if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
return;
@ -3505,51 +3522,54 @@ xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt,
xop->xo_vap);
/*
* For XML and HTML, we need "&<>" processing; for JSON,
* it's quotes. Text gets nothing.
*/
switch (style) {
case XO_STYLE_XML:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
/* FALLTHRU */
case XO_STYLE_HTML:
rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
break;
if (rc > 0) {
/*
* For XML and HTML, we need "&<>" processing; for JSON,
* it's quotes. Text gets nothing.
*/
switch (style) {
case XO_STYLE_XML:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
/* FALLTHRU */
case XO_STYLE_HTML:
rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
break;
case XO_STYLE_JSON:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
rc = xo_escape_json(xbp, rc, 0);
break;
case XO_STYLE_JSON:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
rc = xo_escape_json(xbp, rc, 0);
break;
case XO_STYLE_SDPARAMS:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
rc = xo_escape_sdparams(xbp, rc, 0);
break;
case XO_STYLE_SDPARAMS:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
rc = xo_escape_sdparams(xbp, rc, 0);
break;
case XO_STYLE_ENCODER:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
break;
case XO_STYLE_ENCODER:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
break;
}
/*
* We can assume all the non-%s data we've
* added is ASCII, so the columns and bytes are the
* same. xo_format_string handles all the fancy
* string conversions and updates xo_anchor_columns
* accordingly.
*/
if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += columns;
if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += columns;
}
/*
* We can assume all the non-%s data we've
* added is ASCII, so the columns and bytes are the
* same. xo_format_string handles all the fancy
* string conversions and updates xo_anchor_columns
* accordingly.
*/
if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += columns;
if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += columns;
}
xbp->xb_curp += rc;
if (rc > 0)
xbp->xb_curp += rc;
}
/*
@ -4233,6 +4253,21 @@ xo_format_is_numeric (const char *fmt, ssize_t flen)
return (strchr("diouDOUeEfFgG", *fmt) == NULL) ? FALSE : TRUE;
}
/*
* Update the stack flags using the object flags, allowing callers
* to monkey with the stack flags without even knowing they exist.
*/
static void
xo_stack_set_flags (xo_handle_t *xop)
{
if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
xsp->xs_flags |= XSF_NOT_FIRST;
XOF_CLEAR(xop, XOF_NOT_FIRST);
}
}
static void
xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
{
@ -4337,6 +4372,8 @@ xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
xo_buffer_t *xbp = &xop->xo_data;
xo_humanize_save_t save; /* Save values for humanizing logic */
const char *leader = xo_xml_leader_len(xop, name, nlen);
switch (xo_style(xop)) {
case XO_STYLE_TEXT:
if (flags & XFF_ENCODE_ONLY)
@ -4391,6 +4428,8 @@ xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
if (pretty)
xo_buf_indent(xop, -1);
xo_data_append(xop, "<", 1);
if (*leader)
xo_data_append(xop, leader, 1);
xo_data_escape(xop, name, nlen);
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
@ -4423,6 +4462,8 @@ xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
xo_data_append(xop, "</", 2);
if (*leader)
xo_data_append(xop, leader, 1);
xo_data_escape(xop, name, nlen);
xo_data_append(xop, ">", 1);
if (pretty)
@ -4446,6 +4487,8 @@ xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
flen = strlen(fmt);
}
xo_stack_set_flags(xop);
int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
? 0 : 1;
@ -4751,9 +4794,8 @@ xo_effect_find (const char *str)
static void
xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
{
#ifdef LIBXO_TEXT_ONLY
return;
#endif /* LIBXO_TEXT_ONLY */
if (xo_text_only())
return;
char *cp, *ep, *np, *xp;
ssize_t len = strlen(str);
@ -4837,12 +4879,9 @@ xo_colors_enabled (xo_handle_t *xop UNUSED)
* the incoming foreground and background colors from the map.
*/
static void
xo_colors_update (xo_handle_t *xop, xo_colors_t *newp)
xo_colors_update (xo_handle_t *xop UNUSED, xo_colors_t *newp UNUSED)
{
#ifdef LIBXO_TEXT_ONLY
return;
#endif /* LIBXO_TEXT_ONLY */
#ifndef LIBXO_TEXT_ONLY
xo_color_t fg = newp->xoc_col_fg;
if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS)
fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */
@ -4852,6 +4891,7 @@ xo_colors_update (xo_handle_t *xop, xo_colors_t *newp)
if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS)
bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */
newp->xoc_col_bg = bg;
#endif /* LIBXO_TEXT_ONLY */
}
static void
@ -6454,9 +6494,7 @@ xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
/* If we don't have an anchor, write the text out */
if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
if (xo_write(xop) < 0)
rc = -1; /* Report failure */
else if (xo_flush_h(xop) < 0)
if (xo_flush_h(xop) < 0)
rc = -1;
}
@ -6803,17 +6841,6 @@ xo_attr (const char *name, const char *fmt, ...)
return rc;
}
static void
xo_stack_set_flags (xo_handle_t *xop)
{
if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
xsp->xs_flags |= XSF_NOT_FIRST;
XOF_CLEAR(xop, XOF_NOT_FIRST);
}
}
static void
xo_depth_change (xo_handle_t *xop, const char *name,
int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
@ -6889,6 +6916,15 @@ xo_set_depth (xo_handle_t *xop, int depth)
xop->xo_depth += depth;
xop->xo_indent += depth;
/*
* Handling the "top wrapper" for JSON is a bit of a pain. Here
* we need to detect that the depth has been changed to set the
* "XOIF_TOP_EMITTED" flag correctly.
*/
if (xop->xo_style == XO_STYLE_JSON
&& !XOF_ISSET(xop, XOF_NO_TOP) && xop->xo_depth > 0)
XOIF_SET(xop, XOIF_TOP_EMITTED);
}
static xo_xsf_flags_t
@ -6925,11 +6961,12 @@ xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
name = XO_FAILURE_NAME;
}
const char *leader = xo_xml_leader(xop, name);
flags |= xop->xo_flags; /* Pick up handle flags */
switch (xo_style(xop)) {
case XO_STYLE_XML:
rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
@ -6970,7 +7007,7 @@ xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
return rc;
}
static int
xo_ssize_t
xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
@ -7025,15 +7062,20 @@ xo_do_close_container (xo_handle_t *xop, const char *name)
}
}
const char *leader = xo_xml_leader(xop, name);
switch (xo_style(xop)) {
case XO_STYLE_XML:
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
break;
case XO_STYLE_JSON:
xo_stack_set_flags(xop);
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
ppn = (xop->xo_depth <= 1) ? "\n" : "";
ppn = (xop->xo_depth <= 1) ? pre_nl : "";
ppn = "";
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
@ -7082,7 +7124,7 @@ xo_close_container_d (void)
}
static int
xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xo_do_open_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
ssize_t rc = 0;
int indent = 0;
@ -7126,8 +7168,8 @@ xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
return rc;
}
static int
xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xo_ssize_t
xo_open_list_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
return xo_transition(xop, flags, name, XSS_OPEN_LIST);
}
@ -7228,7 +7270,7 @@ xo_close_list_d (void)
}
static int
xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xo_do_open_leaf_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
ssize_t rc = 0;
int indent = 0;
@ -7322,7 +7364,7 @@ xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
}
static int
xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xo_do_open_instance (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
xop = xo_default(xop);
@ -7330,16 +7372,17 @@ xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
const char *pre_nl = "";
flags |= xop->xo_flags;
if (name == NULL) {
xo_failure(xop, "NULL passed for instance name");
name = XO_FAILURE_NAME;
}
const char *leader = xo_xml_leader(xop, name);
flags |= xop->xo_flags;
switch (xo_style(xop)) {
case XO_STYLE_XML:
rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
@ -7375,8 +7418,8 @@ xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
return rc;
}
static int
xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xo_ssize_t
xo_open_instance_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
{
return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
}
@ -7430,10 +7473,12 @@ xo_do_close_instance (xo_handle_t *xop, const char *name)
}
}
const char *leader = xo_xml_leader(xop, name);
switch (xo_style(xop)) {
case XO_STYLE_XML:
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
break;
case XO_STYLE_JSON:
@ -7599,7 +7644,7 @@ xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
* We are in a given state and need to transition to the new state.
*/
static ssize_t
xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
xo_state_t new_state)
{
xo_stack_t *xsp;
@ -7855,9 +7900,12 @@ xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
/* Handle the flush flag */
if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
if (xo_flush_h(xop))
if (xo_flush_h(xop) < 0)
rc = -1;
/* We have now official made output */
XOIF_SET(xop, XOIF_MADE_OUTPUT);
return rc;
marker_prevents_close:
@ -7950,7 +7998,7 @@ xo_flush (void)
xo_ssize_t
xo_finish_h (xo_handle_t *xop)
{
const char *cp = "";
const char *open_if_empty = "";
xop = xo_default(xop);
if (!XOF_ISSET(xop, XOF_NO_CLOSE))
@ -7959,11 +8007,17 @@ xo_finish_h (xo_handle_t *xop)
switch (xo_style(xop)) {
case XO_STYLE_JSON:
if (!XOF_ISSET(xop, XOF_NO_TOP)) {
const char *pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
else
cp = "{ ";
xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
else if (!XOIF_ISSET(xop, XOIF_MADE_OUTPUT)) {
open_if_empty = "{ ";
pre_nl = "";
}
xo_printf(xop, "%s%*s%s}\n",
pre_nl, xo_indent(xop), "", open_if_empty);
}
break;
@ -8402,3 +8456,46 @@ xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
xop->xo_style = XO_STYLE_ENCODER;
xop->xo_encoder = encoder;
}
/*
* The xo(1) utility needs to be able to open and close lists and
* instances, but since it's called without "state", we cannot
* rely on the state transitions (in xo_transition) to DTRT, so
* we have a mechanism for external parties to "force" transitions
* that would otherwise be impossible. This is not a general
* mechanism, and is really tailored only for xo(1).
*/
void
xo_explicit_transition (xo_handle_t *xop, xo_state_t new_state,
const char *name, xo_xof_flags_t flags)
{
xo_xsf_flags_t xsf_flags;
xop = xo_default(xop);
switch (new_state) {
case XSS_OPEN_LIST:
xo_do_open_list(xop, flags, name);
break;
case XSS_OPEN_INSTANCE:
xo_do_open_instance(xop, flags, name);
break;
case XSS_CLOSE_INSTANCE:
xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE,
xo_stack_flags(flags));
xo_stack_set_flags(xop);
xo_do_close_instance(xop, name);
break;
case XSS_CLOSE_LIST:
xsf_flags = XOF_ISSET(xop, XOF_NOT_FIRST) ? XSF_NOT_FIRST : 0;
xo_depth_change(xop, name, 1, 1, XSS_OPEN_LIST,
XSF_LIST | xsf_flags | xo_stack_flags(flags));
xo_do_close_list(xop, name);
break;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014-2015, Juniper Networks, Inc.
* Copyright (c) 2014-2018, Juniper Networks, Inc.
* All rights reserved.
* This SOFTWARE is licensed under the LICENSE provided in the
* ../Copyright file. By downloading, installing, copying, or otherwise
@ -102,6 +102,7 @@ typedef unsigned long long xo_xof_flags_t;
#define XOF_RETAIN_NONE XOF_BIT(31) /** Prevent use of XOEF_RETAIN */
#define XOF_COLOR_MAP XOF_BIT(32) /** Color map has been initialized */
#define XOF_CONTINUATION XOF_BIT(33) /** Continuation of previous line */
typedef unsigned xo_emit_flags_t; /* Flags to xo_emit() and friends */
#define XOEF_RETAIN (1<<0) /* Retain parsed formatting information */
@ -126,11 +127,11 @@ typedef struct xo_handle_s xo_handle_t; /* Handle for XO output */
* sizes. We want to fix this but allow for backwards compatibility
* where needed.
*/
#ifdef USE_INT_RETURN_CODES
#ifdef XO_USE_INT_RETURN_CODES
typedef int xo_ssize_t; /* Buffer size */
#else /* USE_INT_RETURN_CODES */
#else /* XO_USE_INT_RETURN_CODES */
typedef ssize_t xo_ssize_t; /* Buffer size */
#endif /* USE_INT_RETURN_CODES */
#endif /* XO_USE_INT_RETURN_CODES */
typedef xo_ssize_t (*xo_write_func_t)(void *, const char *);
typedef void (*xo_close_func_t)(void *);
@ -219,36 +220,36 @@ xo_ssize_t
xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...);
PRINTFLIKE(2, 0)
static inline int
static inline xo_ssize_t
xo_emit_hvp (xo_handle_t *xop, const char *fmt, va_list vap)
{
return xo_emit_hv(xop, fmt, vap);
}
PRINTFLIKE(2, 3)
static inline int
static inline xo_ssize_t
xo_emit_hp (xo_handle_t *xop, const char *fmt, ...)
{
va_list vap;
va_start(vap, fmt);
int rc = xo_emit_hv(xop, fmt, vap);
xo_ssize_t rc = xo_emit_hv(xop, fmt, vap);
va_end(vap);
return rc;
}
PRINTFLIKE(1, 2)
static inline int
static inline xo_ssize_t
xo_emit_p (const char *fmt, ...)
{
va_list vap;
va_start(vap, fmt);
int rc = xo_emit_hv(NULL, fmt, vap);
xo_ssize_t rc = xo_emit_hv(NULL, fmt, vap);
va_end(vap);
return rc;
}
PRINTFLIKE(3, 0)
static inline int
static inline xo_ssize_t
xo_emit_hvfp (xo_handle_t *xop, xo_emit_flags_t flags,
const char *fmt, va_list vap)
{
@ -256,27 +257,30 @@ xo_emit_hvfp (xo_handle_t *xop, xo_emit_flags_t flags,
}
PRINTFLIKE(3, 4)
static inline int
static inline xo_ssize_t
xo_emit_hfp (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
{
va_list vap;
va_start(vap, fmt);
int rc = xo_emit_hvf(xop, flags, fmt, vap);
xo_ssize_t rc = xo_emit_hvf(xop, flags, fmt, vap);
va_end(vap);
return rc;
}
PRINTFLIKE(2, 3)
static inline int
static inline xo_ssize_t
xo_emit_fp (xo_emit_flags_t flags, const char *fmt, ...)
{
va_list vap;
va_start(vap, fmt);
int rc = xo_emit_hvf(NULL, flags, fmt, vap);
xo_ssize_t rc = xo_emit_hvf(NULL, flags, fmt, vap);
va_end(vap);
return rc;
}
xo_ssize_t
xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name);
xo_ssize_t
xo_open_container_h (xo_handle_t *xop, const char *name);
@ -301,6 +305,9 @@ xo_close_container_hd (xo_handle_t *xop);
xo_ssize_t
xo_close_container_d (void);
xo_ssize_t
xo_open_list_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name);
xo_ssize_t
xo_open_list_h (xo_handle_t *xop, const char *name);
@ -325,6 +332,9 @@ xo_close_list_hd (xo_handle_t *xop);
xo_ssize_t
xo_close_list_d (void);
xo_ssize_t
xo_open_instance_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name);
xo_ssize_t
xo_open_instance_h (xo_handle_t *xop, const char *name);

View File

@ -17,11 +17,11 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_attr "const char *name" "const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_attr_h "xo_handle_t *handle" "const char *name, const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_attr_hv "xo_handle_t *handle" "const char *name" "const char *fmt" "va_list vap"
.Sh DESCRIPTION
The

View File

@ -114,7 +114,12 @@ static inline int
xo_buf_has_room (xo_buffer_t *xbp, ssize_t len)
{
if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) {
ssize_t sz = xbp->xb_size + XO_BUFSIZ;
/*
* Find out how much new space we need, round it up to XO_BUFSIZ
*/
ssize_t sz = (xbp->xb_curp + len) - xbp->xb_bufp;
sz = (sz + XO_BUFSIZ - 1) & ~(XO_BUFSIZ - 1);
char *bp = xo_realloc(xbp->xb_bufp, sz);
if (bp == NULL)
return 0;

View File

@ -17,11 +17,11 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_emit "const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_emit_h "xo_handle_t *xop" "const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_emit_hv "xo_handle_t *xop" "const char *fmt" "va_list vap"
.Sh DESCRIPTION
The

View File

@ -17,11 +17,11 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_emit_f "xo_emit_flags_t flags" "const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_emit_hf "xo_handle_t *xop" "xo_emit_flags_t flags" "const char *fmt" "..."
.Ft int
.Ft xo_ssize_t
.Fn xo_emit_hvf "xo_handle_t *xop" "xo_emit_flags_t flags" "const char *fmt" "va_list vap"
.Ft void
.Fn xo_retain_clear_all "void"

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2015, Juniper Networks, Inc.
* All rights reserved.
* This SOFTWARE is licensed under the LICENSE provided in the
* ../Copyright file. By downloading, installing, copying, or otherwise
* using the SOFTWARE, you agree to be bound by the terms of that
* LICENSE.
*
* Phil Shafer, March 2019
*/
#ifndef XO_EXPLICIT_H
#define XO_EXPLICIT_H
/*
* NOTE WELL: This file is needed to software that implements an
* explicit transition between libxo states on its internal stack.
* General libxo code should _never_ include this header file.
*/
/*
* A word about states: We use a finite state machine (FMS) approach
* to help remove fragility from the caller's code. Instead of
* requiring a specific order of calls, we'll allow the caller more
* flexibility and make the library responsible for recovering from
* missed steps. The goal is that the library should not be capable
* of emitting invalid xml or json, but the developer shouldn't need
* to know or understand all the details about these encodings.
*
* You can think of states as either states or events, since they
* function rather like both. None of the XO_CLOSE_* events will
* persist as states, since the matching stack frame will be popped.
* Same is true of XSS_EMIT, which is an event that asks us to
* prep for emitting output fields.
*/
/* Stack frame states */
typedef unsigned xo_state_t; /* XSS_* values */
#define XSS_INIT 0 /* Initial stack state */
#define XSS_OPEN_CONTAINER 1
#define XSS_CLOSE_CONTAINER 2
#define XSS_OPEN_LIST 3
#define XSS_CLOSE_LIST 4
#define XSS_OPEN_INSTANCE 5
#define XSS_CLOSE_INSTANCE 6
#define XSS_OPEN_LEAF_LIST 7
#define XSS_CLOSE_LEAF_LIST 8
#define XSS_DISCARDING 9 /* Discarding data until recovered */
#define XSS_MARKER 10 /* xo_open_marker's marker */
#define XSS_EMIT 11 /* xo_emit has a leaf field */
#define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */
#define XSS_FINISH 13 /* xo_finish was called */
#define XSS_MAX 13
void
xo_explicit_transition (xo_handle_t *xop, xo_state_t new_state,
const char *tag, xo_xof_flags_t flags);
#endif /* XO_EXPLICIT_H */

View File

@ -17,9 +17,9 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft void
.Ft xo_ssize_t
.Fn xo_finish "void"
.Ft void
.Ft xo_ssize_t
.Fn xo_finish_h "xo_handle_t *xop"
.Sh DESCRIPTION
When the program is ready to exit or close a handle, a call to

View File

@ -17,9 +17,9 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft void
.Ft xo_ssize_t
.Fn xo_flush "void"
.Ft void
.Ft xo_ssize_t
.Fn xo_flush_h "xo_handle_t *handle"
.Sh DESCRIPTION
.Nm libxo

View File

@ -18,21 +18,21 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_open_container "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_container_h "xo_handle_t *handle" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_container_hd "xo_handle_t *handle" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_container_d "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_container "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_container_h "xo_handle_t *handle" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_container_hd "xo_handle_t *handle"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_container_d "void"
.Sh DESCRIPTION
.Nm libxo

View File

@ -20,37 +20,37 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_open_list_h "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_list "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_list_hd "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_list_d "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_instance_h "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_instance "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_instance_hd "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_instance_d "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_instance_h "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_instance "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_instance_hd "xo_handle_t *xop"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_instance_d "void"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_list_h "xo_handle_t *xop" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_list "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_list_hd "xo_handle_t *xop"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_list_d "void"
.Sh DESCRIPTION
Lists are sequences of instances of homogeneous data objects.

View File

@ -17,13 +17,13 @@
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
.Ft int
.Ft xo_ssize_t
.Fn xo_open_marker "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_open_marker_h "xo_handle_t *handle" "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_marker "const char *name"
.Ft int
.Ft xo_ssize_t
.Fn xo_close_marker_h "xo_handle_t *handle" "const char *name"
.Sh DESCRIPTION
.Nm libxo

View File

@ -18,7 +18,7 @@
.Sh SYNOPSIS
.In libxo/xo.h
.Ft void
.Sy typedef int (*xo_write_func_t)(void *, const char *);
.Sy typedef xo_ssize_t (*xo_write_func_t)(void *, const char *);
.Pp
.Sy typedef void (*xo_close_func_t)(void *);
.Pp

View File

@ -1,2 +1 @@
{"top": {"type":"ethernet","type":"bridge","type":"18u","type":24,"address":"0x0","port":1,"address":"0x0","port":1,"address":"0x0","port":1,"used-percent":12,"kve_start":"0xdeadbeef","kve_end":"0xcabb1e","host":"my-box","domain":"example.com","host":"my-box","domain":"example.com","label":"value","max-chaos":"very","min-chaos":42,"some-chaos":"[42]","host":"my-box","domain":"example.com", "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data2": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data3": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data4": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455,"mode":"mode","mode_octal":"octal","links":"links","user":"user","group":"group","pre":"that","links":3,"post":"this","mode":"/some/file","mode_octal":640,"links":1,"user":"user","group":"group"}
}
{"top": {"type":"ethernet","type":"bridge","type":"18u","type":24,"address":"0x0","port":1,"address":"0x0","port":1,"address":"0x0","port":1,"used-percent":12,"kve_start":"0xdeadbeef","kve_end":"0xcabb1e","host":"my-box","domain":"example.com","host":"my-box","domain":"example.com","label":"value","max-chaos":"very","min-chaos":42,"some-chaos":"[42]","host":"my-box","domain":"example.com", "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data2": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data3": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data4": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455,"mode":"mode","mode_octal":"octal","links":"links","user":"user","group":"group","pre":"that","links":3,"post":"this","mode":"/some/file","mode_octal":640,"links":1,"user":"user","group":"group"}}

View File

@ -1,2 +1 @@
{"top": {"data": {"what":"braces","length":"abcdef","fd":-1,"error":"Bad file descriptor","test":"good","fd":-1,"error":"Bad fi","test":"good","lines":20,"words":30,"characters":40, "bytes": [0,1,2,3,4],"mbuf-current":10,"mbuf-cache":20,"mbuf-total":30,"distance":50,"location":"Boston","memory":64,"total":640,"memory":64,"total":640,"ten":10,"eleven":11,"unknown":1010,"unknown":1010,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"val1":21,"val2":58368,"val3":100663296,"val4":44470272,"val5":1342172800, "flag": ["one","two","three"],"works":null,"empty-tag":true,"t1":"1000","t2":"test5000","t3":"ten-longx","t4":"xtest", "__error": {"message":"this is an error"}, "__error": {"message":"two more errors"}, "__warning": {"message":"this is an warning"}, "__warning": {"message":"two more warnings"},"count":10,"test":4, "error": {"message":"Shut 'er down, Clancey! She's a-pumpin' mud! <>!,\"!<>\n"}}}
}
{"top": {"data": {"what":"braces","length":"abcdef","fd":-1,"error":"Bad file descriptor","test":"good","fd":-1,"error":"Bad fi","test":"good","lines":20,"words":30,"characters":40, "bytes": [0,1,2,3,4],"mbuf-current":10,"mbuf-cache":20,"mbuf-total":30,"distance":50,"location":"Boston","memory":64,"total":640,"memory":64,"total":640,"ten":10,"eleven":11,"unknown":1010,"unknown":1010,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"val1":21,"val2":58368,"val3":100663296,"val4":44470272,"val5":1342172800, "flag": ["one","two","three"],"works":null,"empty-tag":true,"t1":"1000","t2":"test5000","t3":"ten-longx","t4":"xtest", "__error": {"message":"this is an error"}, "__error": {"message":"two more errors"}, "__warning": {"message":"this is an warning"}, "__warning": {"message":"two more warnings"},"count":10,"test":4, "error": {"message":"Shut 'er down, Clancey! She's a-pumpin' mud! <>!,\"!<>\n"}}}}

View File

@ -1,2 +1 @@
{"employees": {"employee": [],"extra":"", "memory": [{"type":"name","in-use":12345,"memory-use":54321,"high-use":"-","requests":32145}], "employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}
}
{"employees": {"employee": [],"extra":"", "memory": [{"type":"name","in-use":12345,"memory-use":54321,"high-use":"-","requests":32145}], "employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}}

View File

@ -1,2 +1 @@
{"employees": {"employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}
}
{"employees": {"employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}}

View File

@ -1,3 +1 @@
{"indian-languages": {"gurmukhi":"ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।","shahmukhi":"لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔","tranliteration":"lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē."}
, "employees": {"wc": ["෴ - 0xdf4 - 1","ණ - 0xdab - 1","් - 0xdca - 0","ණ - 0xdab - 1","្ - 0x17d2 - 0","෴ - 0xdf4 - 1","1 - 0x31 - 1","͏ - 0x34f - 0","2 - 0x32 - 1","⃝ - 0x20dd - 0"],"fancy":"1͏2⃝","v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","width":55,"sinhala":"෴ණ්ණ෴","width":4,"sinhala":"෴","width":1,"sinhala":"෴ණ්ණ෴෴ණ්ණ෴","width":8,"not-sinhala":"123456","tag":"ර්‍ඝ","width":2, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"benefits":"full"}, {"first-name":"෴ණ්ණ෴෴ණ්ණ෴","nic-name":"\"Mick\"","last-name":"෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴","department":110,"percent-time":20}]}
}
{"indian-languages": {"gurmukhi":"ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।","shahmukhi":"لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔","tranliteration":"lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē."}, "employees": {"wc": ["෴ - 0xdf4 - 1","ණ - 0xdab - 1","් - 0xdca - 0","ණ - 0xdab - 1","្ - 0x17d2 - 0","෴ - 0xdf4 - 1","1 - 0x31 - 1","͏ - 0x34f - 0","2 - 0x32 - 1","⃝ - 0x20dd - 0"],"fancy":"1͏2⃝","v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","width":55,"sinhala":"෴ණ්ණ෴","width":4,"sinhala":"෴","width":1,"sinhala":"෴ණ්ණ෴෴ණ්ණ෴","width":8,"not-sinhala":"123456","tag":"ර්‍ඝ","width":2, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"benefits":"full"}, {"first-name":"෴ණ්ණ෴෴ණ්ණ෴","nic-name":"\"Mick\"","last-name":"෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴","department":110,"percent-time":20}]}}

View File

@ -3,8 +3,7 @@
"gurmukhi": "ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।",
"shahmukhi": "لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔",
"tranliteration": "lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē."
}
,
},
"employees": {
"wc": [
"෴ - 0xdf4 - 1",

View File

@ -1,2 +1 @@
{"employees": {"employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}
}
{"employees": {"employee": [{"first-name":"Terry","last-name":"Jones","department":660}, {"first-name":"Leslie","last-name":"Patterson","department":341}, {"first-name":"Ashley","last-name":"Smith","department":1440}]}}

View File

@ -1,2 +1 @@
{"employees": {"test": [{"filename":"(null)"}],"v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","columns":28,"columns":2,"v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","columns":55,"columns":0, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"columns":23,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"columns":47,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"columns":25,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40,"columns":30}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40,"columns":49}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"columns":29,"benefits":"full"}]}
}
{"employees": {"test": [{"filename":"(null)"}],"v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","columns":28,"columns":2,"v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","columns":55,"columns":0, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"columns":23,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"columns":47,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"columns":25,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40,"columns":30}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40,"columns":49}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"columns":29,"benefits":"full"}]}}

View File

@ -1,2 +1 @@
{"top": {"data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data2": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data3": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17,"test":"one"}]}}, "data4": {"contents": {"item": [{"name":"gum","count":1412, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"rope","count":85, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"ladder","count":0, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"bolt","count":4123, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"water","count":17, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3,"test":"one"}]}}}
}
{"top": {"data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data2": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data3": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17,"test":"one"}]}}, "data4": {"contents": {"item": [{"name":"gum","count":1412, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"rope","count":85, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"ladder","count":0, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"bolt","count":4123, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"water","count":17, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3,"test":"one"}]}}}}

View File

@ -1,2 +1 @@
{"top": {"data": {"contents": {"name": ["gum","rope","ladder","bolt","water"]}, "contents": {"item": ["gum","rope","ladder","bolt","water"]}, "contents": {"item": ["gum","rope","ladder","bolt","water"],"total":"six","one":"one", "two": ["two"],"three":"three"}}}
}
{"top": {"data": {"contents": {"name": ["gum","rope","ladder","bolt","water"]}, "contents": {"item": ["gum","rope","ladder","bolt","water"]}, "contents": {"item": ["gum","rope","ladder","bolt","water"],"total":"six","one":"one", "two": ["two"],"three":"three"}}}}

View File

@ -1,2 +1 @@
{"__version": "3.1.4", "top": {"data": {"item": [],"data":"bold","data":"bold-ul","data":"triple","data":"inv-ul","data":"underline","data":"plain", "item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455}
}
{"__version": "3.1.4", "top": {"data": {"item": [],"data":"bold","data":"bold-ul","data":"triple","data":"inv-ul","data":"underline","data":"plain", "item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455}}

View File

@ -14,5 +14,4 @@
{{test-program: }}
{{An application 1011 log entry}}
{"__version": "3.1.4", "top": {}
}
{"__version": "3.1.4", "top": {}}

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

View File

@ -83,6 +83,10 @@ op content: [time] [2:15] [0]
op string: [hand] [left] [0]
op string: [color] [blue] [0]
op content: [time] [3:45] [0]
op open_container: [2by4] [] [0x4040010]
op string: [4x4] [truck] [0]
op string: [2morrow] [tomorrow] [0]
op close_container: [2by4] [] [0]
op close_instance: [thing] [] [0]
op close_list: [thing] [] [0]
op close_container: [data] [] [0]

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

View File

@ -167,3 +167,9 @@
<div class="text"> til </div>
<div class="data" data-tag="time" data-xpath="/top/data/thing[name = 'thing']/time">03:45</div>
</div>
<div class="line">
<div class="text">There is </div>
<div class="data" data-tag="4x4" data-xpath="/top/data/thing[name = 'thing']/2by4/4x4">truck</div>
<div class="text"> in </div>
<div class="data" data-tag="2morrow" data-xpath="/top/data/thing[name = 'thing']/2by4/2morrow">tomorrow</div>
</div>

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

View File

@ -167,3 +167,9 @@
<div class="text"> til </div>
<div class="data" data-tag="time">03:45</div>
</div>
<div class="line">
<div class="text">There is </div>
<div class="data" data-tag="4x4">truck</div>
<div class="text"> in </div>
<div class="data" data-tag="2morrow">tomorrow</div>
</div>

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

View File

@ -1,2 +1 @@
{"top": {"data": {"animal":"fish","animal":"fish", "thing": [{"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}]}}
}
{"top": {"data": {"animal":"fish","animal":"fish", "thing": [{"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45"}, {"name":"thing","color":"green","time":"2:15","hand":"left","color":"blue","time":"3:45", "2by4": {"4x4":"truck","2morrow":"tomorrow"}}]}}}

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

View File

@ -82,7 +82,11 @@
"time": "2:15",
"hand": "left",
"color": "blue",
"time": "3:45"
"time": "3:45",
"2by4": {
"4x4": "truck",
"2morrow": "tomorrow"
}
}
]
}

View File

@ -0,0 +1,4 @@
test_12.test: invalid XML tag name: '2by4'
test_12.test: invalid XML tag name: '4x4'
test_12.test: invalid XML tag name: '2morrow'
test_12.test: invalid XML tag name: '2by4'

Some files were not shown because too many files have changed in this diff Show More