[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[Libvir] diagnose invalid domain ID numbers (and invalid integers, in general)



Hello,

There are over 30 uses of strtol in libvirt, and they all can silently
accept invalid input.  The invalid string might range from an outlandish
domain ID like 4294967298 to strings of digits followed by bogus alpha.
Maybe not worth worrying about, you say?  But what if they indicate user
confusion, e.g., 1,000 vs 1000?  Silently interpreting "1,000" as "1"
would leave the poor user even more confused :-)  IMHO, they should all
be diagnosed.

Fixing them properly requires some infrastructure,
so that you don't end up duplicating too much logic.

This patch adds part of that infrastructure and fixes only a single
instance, to start with.  I'll fix the others once we're all agreed
on the form of the infrastructure.  I've fixed the bug that would make
this command:

    echo domname $(echo 2^32+2|bc)|src/virsh ...

act just like this one:

    echo domname 2|src/virsh ...

Now, it does this:

    $ echo domname 4294967298|src/virsh --quiet --connect test://$PWD/docs/testnode.xml
    virsh > error: failed to get domain '4294967298'
    virsh >

The new test script, tests/int-overflow demonstrates
precisely that before/after behavior.

This change adds some other new files.
src/xstrtol.c and .h contain the first of a few new
strtol-like functions.  This first one, xstrtol_i,
converts to an "int".  Other wrappers will convert to wider types.
The goal is to put the tedious tests into the wrappers so that
applications can be robust without all the duplicated gore.

It is important (from type-safety and maintainability standpoints) to
ensure that the resulting integral value be "returned" via an argument
pointer, not a return value.  With the former, you're guaranteed to have
matching types.  Simply getting the value via "return", it is too easy
to mistakenly change width or signedness.

Here's the new function's description and signature:

  /* Like strtol, but produce an "int" result, and check more carefully.
     Return 0 upon success;  return -1 to indicate failure.
     When END_PTR is NULL, the byte after the final valid digit must be NUL.
     Otherwise, it's like strtol and lets the caller check any suffix for
     validity.  This function is careful to return -1 when the string S
     represents a number that is not representable as an "int". */
  int xstrtol_i(char const *s, char **end_ptr, int base, int *result)

My first attempt put the definition of xstrtol_i in
the logical place: util.c.  But that would have required linking
virsh with util.c, and that fails due to an unsatisfied reference
to __virRaiseError.  So instead, it's in its own file, now.
If you want it in some other file, just tell me where.

BTW, I have no strong preference for the name xstrtol_i.  Bear in mind
that the name tells you that it's based on (and limited to) strtol's
"long" type, and produces an 'i'nt value.  It happens to be the same one
used in at least one other project, and closely resembles the xstrto*
functions in gnulib.  Note that there are almost certainly places in the
code where we'll want to use a variant that targets an unsigned type like
"size_t" or a wider type like long long.  In addition to other uses of
strtol that I plan to fix, there are three uses of strtoll, too.

Patch attached below.
If you apply it with plain-old-patch, remember to run this:

chmod a+x tests/int-overflow

Thu Nov  8 09:59:43 CET 2007 Jim Meyering  <meyering redhat com>

	Diagnose an invalid domain ID number.

	* src/virsh.c: Include "xstrtol.h"
	(vshCommandOptDomainBy): Detect integer overflow in domain ID number.
	* tests/int-overflow: New script. Test for the above-fixed bug.
	* tests/Makefile.am (TESTS): Add int-overflow.
	(TESTS_ENVIRONMENT): Define, to propagate $abs_top_* variables
	into the int-overflow script.
	(valgrind): Adapt rule not to clobber new TESTS_ENVIRONMENT.
	* src/xstrtol.h, src/xstrtol.c: New files.
	* src/Makefile.am (virsh_SOURCES): Add xstrtol.c and xstrtol.h.

---
 src/Makefile.am    |    2 +-
 src/virsh.c        |   10 ++++------
 src/xstrtol.c      |   33 +++++++++++++++++++++++++++++++++
 src/xstrtol.h      |    7 +++++++
 tests/Makefile.am  |   10 ++++++++--
 tests/int-overflow |   22 ++++++++++++++++++++++
 6 files changed, 75 insertions(+), 9 deletions(-)
 create mode 100644 src/xstrtol.c
 create mode 100644 src/xstrtol.h
 create mode 100755 tests/int-overflow

diff --git a/src/Makefile.am b/src/Makefile.am
index e2d6164..ae0b0a8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -67,7 +67,7 @@ libvirt_la_SOURCES = $(CLIENT_SOURCES) $(SERVER_SOURCES)
 
 bin_PROGRAMS = virsh
 
-virsh_SOURCES = virsh.c console.c console.h
+virsh_SOURCES = virsh.c console.c console.h xstrtol.c xstrtol.h
 virsh_LDFLAGS = $(WARN_CFLAGS) $(COVERAGE_LDFLAGS)
 virsh_DEPENDENCIES = $(DEPS)
 virsh_LDADD = $(LDADDS) $(VIRSH_LIBS)
diff --git a/src/virsh.c b/src/virsh.c
index 5327a28..1f15ee4 100644
--- a/src/virsh.c
+++ b/src/virsh.c
@@ -44,6 +44,7 @@
 #include "config.h"
 #include "internal.h"
 #include "console.h"
+#include "xstrtol.h"
 
 static char *progname;
 
@@ -2961,10 +2962,8 @@ cmdVNCDisplay(vshControl * ctl, vshCmd * cmd)
         (obj->stringval == NULL) || (obj->stringval[0] == 0)) {
         goto cleanup;
     }
-    port = strtol((const char *)obj->stringval, NULL, 10);
-    if (port == -1) {
+    if (xstrtol_i((const char *)obj->stringval, NULL, 10, &port) || port < 0)
         goto cleanup;
-    }
     xmlXPathFreeObject(obj);
 
     obj = xmlXPathEval(BAD_CAST "string(/domain/devices/graphics[ type='vnc']/@listen)", ctxt);
@@ -3997,7 +3996,7 @@ vshCommandOptDomainBy(vshControl * ctl, vshCmd * cmd, const char *optname,
                       char **name, int flag)
 {
     virDomainPtr dom = NULL;
-    char *n, *end = NULL;
+    char *n;
     int id;
 
     if (!(n = vshCommandOptString(cmd, optname, NULL))) {
@@ -4013,8 +4012,7 @@ vshCommandOptDomainBy(vshControl * ctl, vshCmd * cmd, const char *optname,
 
     /* try it by ID */
     if (flag & VSH_BYID) {
-        id = (int) strtol(n, &end, 10);
-        if (id >= 0 && end && *end == '\0') {
+        if (xstrtol_i(n, NULL, 10, &id) == 0 && id >= 0) {
             vshDebug(ctl, 5, "%s: <%s> seems like domain ID\n",
                      cmd->def->name, optname);
             dom = virDomainLookupByID(ctl->conn, id);
diff --git a/src/xstrtol.c b/src/xstrtol.c
new file mode 100644
index 0000000..1f0a8ec
--- /dev/null
+++ b/src/xstrtol.c
@@ -0,0 +1,33 @@
+/*
+ * xstrtol.c: strtol wrappers
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+#include <config.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "xstrtol.h"
+
+/* Like strtol, but produce an "int" result, and check more carefully.
+   Return 0 upon success;  return -1 to indicate failure.
+   When END_PTR is NULL, the byte after the final valid digit must be NUL.
+   Otherwise, it's like strtol and lets the caller check any suffix for
+   validity.  This function is careful to return -1 when the string S
+   represents a number that is not representable as an "int". */
+int xstrtol_i(char const *s, char **end_ptr, int base, int *result)
+{
+    long int val;
+    char *p;
+    int err;
+
+    errno = 0;
+    val = strtol(s, &p, base);
+    err = (errno || (!end_ptr && *p) || p == s || (int) val != val);
+    if (end_ptr)
+        *end_ptr = p;
+    if (err)
+        return -1;
+    *result = val;
+    return 0;
+}
diff --git a/src/xstrtol.h b/src/xstrtol.h
new file mode 100644
index 0000000..2bfb812
--- /dev/null
+++ b/src/xstrtol.h
@@ -0,0 +1,7 @@
+/*
+ * xstrtol.h: strtol wrappers
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+int xstrtol_i(char const *s, char **end_ptr, int base, int *result);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 80692e0..8a472f8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -38,13 +38,19 @@ noinst_PROGRAMS = xmlrpctest xml2sexprtest sexpr2xmltest virshtest conftest \
         nodeinfotest
 
 TESTS = xml2sexprtest sexpr2xmltest virshtest test_conf.sh xmconfigtest \
-        xencapstest qemuxml2argvtest qemuxml2xmltest nodeinfotest
+        xencapstest qemuxml2argvtest qemuxml2xmltest nodeinfotest \
+	int-overflow
 if ENABLE_XEN_TESTS
   TESTS += reconnect
 endif
 
+TESTS_ENVIRONMENT =				\
+  abs_top_builddir='$(abs_top_builddir)'	\
+  abs_top_srcdir='$(abs_top_srcdir)'		\
+  $(VG)
+
 valgrind:
-	$(MAKE) check TESTS_ENVIRONMENT="valgrind --quiet --leak-check=full"
+	$(MAKE) check VG="valgrind --quiet --leak-check=full"
 
 # Note: xmlrpc.[c|h] is not in libvirt yet
 xmlrpctest_SOURCES = \
diff --git a/tests/int-overflow b/tests/int-overflow
new file mode 100755
index 0000000..97a1ab2
--- /dev/null
+++ b/tests/int-overflow
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Ensure that an invalid domain ID isn't interpreted as a valid one.
+# Before, an ID of 2^32+2 would be treated just like an ID of 2.
+
+# Boilerplate code to set up a test directory, cd into it,
+# and to ensure we remove it upon completion.
+this_test_() { echo "./$0" | sed 's,.*/,,'; }
+t_=$(this_test_)-$$
+init_cwd_=$(pwd)
+trap 'st=$?; d='"$t_"';
+    cd '"$init_cwd_"' && chmod -R u+rwx "$d" && rm -rf "$d" && exit $st' 0
+trap '(exit $?); exit $?' 1 2 13 15
+mkdir "$t_" || fail=1
+cd "$t_" || fail=1
+
+echo "error: failed to get domain '4294967298'" > exp || fail=1
+echo domname 4294967298 | $abs_top_builddir/src/virsh --quiet \
+    --connect test://$abs_top_srcdir/docs/testnode.xml \
+    > /dev/null 2> err || fail=1
+diff -u err exp || fail=1
+
+exit $fail
-- 
1.5.3.5.602.g53d1

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]