Signed-off-by: Jakub Filak jfilak@redhat.com --- src/dbus/abrt-dbus.c | 152 ++++++++++++- tests/runtests/aux/test_order | 1 + tests/runtests/dbus-elements-handling/PURPOSE | 7 + tests/runtests/dbus-elements-handling/runtest.sh | 278 +++++++++++++++++++++++ 4 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 tests/runtests/dbus-elements-handling/PURPOSE create mode 100755 tests/runtests/dbus-elements-handling/runtest.sh
diff --git a/src/dbus/abrt-dbus.c b/src/dbus/abrt-dbus.c index 425409f..d952e83 100644 --- a/src/dbus/abrt-dbus.c +++ b/src/dbus/abrt-dbus.c @@ -36,6 +36,15 @@ static const gchar introspection_xml[] = " <arg type='as' name='element_names' direction='in'/>" " <arg type='a{ss}' name='response' direction='out'/>" " </method>" + " <method name='SetElement'>" + " <arg type='s' name='problem_dir' direction='in'/>" + " <arg type='s' name='name' direction='in'/>" + " <arg type='s' name='value' direction='in'/>" + " </method>" + " <method name='DeleteElement'>" + " <arg type='s' name='problem_dir' direction='in'/>" + " <arg type='s' name='name' direction='in'/>" + " </method>" " <method name='ChownProblemDir'>" " <arg type='s' name='problem_dir' direction='in'/>" " </method>" @@ -154,7 +163,9 @@ static bool uid_in_group(uid_t uid, gid_t gid) static int dir_accessible_by_uid(const char *dir_path, uid_t uid) { struct stat statbuf; - if (stat(dir_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) + if (stat(dir_path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) + errno = ENOTDIR; + else { if (uid == 0 || (statbuf.st_mode & S_IROTH) || uid_in_group(uid, statbuf.st_gid)) { @@ -403,6 +414,65 @@ static void return_InvalidProblemDir_error(GDBusMethodInvocation *invocation, co free(msg); }
+/* + * Checks element's rights and does not open directory if element is protected. + * Checks problem's rights and does not open directory if user hasn't got + * access to a problem. + * + * Returns a dump directory opened for writing or NULL. + * + * If any operation from the above listed fails, immediately returns D-Bus + * error to a D-Bus caller. + */ +static struct dump_dir *open_directory_for_modification_of_element( + GDBusMethodInvocation *invocation, + uid_t caller_uid, + const char *problem_id, + const char *element) +{ + static const char *const protected_elements[] = { + FILENAME_TIME, + FILENAME_UID, + NULL, + }; + + for (const char *const *protected = protected_elements; *protected; ++protected) + { + if (strcmp(*protected, element) == 0) + { + char *error = xasprintf(_("'%s' element can't be modified"), element); + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.ProtectedElement", + error); + free(error); + return NULL; + } + } + + if (!dir_accessible_by_uid(problem_id, caller_uid)) + { + if (errno == ENOTDIR) + return_InvalidProblemDir_error(invocation, problem_id); + else + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.AuthFailure", + _("Not Authorized")); + + return NULL; + } + + struct dump_dir *dd = dd_opendir(problem_id, /* flags : */ 0); + if (!dd) + { /* This should not happen because of the access check above */ + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.Failure", + _("Can't access the problem for modification")); + return NULL; + } + + return dd; +} + static void handle_method_call(GDBusConnection *connection, const gchar *caller, const gchar *object_path, @@ -620,6 +690,86 @@ static void handle_method_call(GDBusConnection *connection, return; }
+ if (g_strcmp0(method_name, "SetElement") == 0) + { + const char *problem_id; + const char *element; + const char *value; + + g_variant_get(parameters, "(&s&s&s)", &problem_id, &element, &value); + + if (element == NULL || element[0] == '\0' || strlen(element) > 64) + { + char *error = xasprintf(_("'%s' is not a valid element name"), element); + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.InvalidElement", + error); + + free(error); + return; + } + + struct dump_dir *dd = open_directory_for_modification_of_element( + invocation, caller_uid, problem_id, element); + if (!dd) + return; + + /* Is it good idea to make it static? Is it possible to change the max size while a single run? */ + const double max_size = g_settings_nMaxCrashReportsSize * (1024 * 1024); + + const double requested_size = strlen(value) - dd_get_item_size(dd, element); + /* Don't want to check the size limit in case of reducing of size */ + if (requested_size > 0 + && requested_size > (max_size - get_dirsize(g_settings_dump_location))) + { + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.Failure", + _("No problem space left")); + } + else + { + dd_save_text(dd, element, value); + g_dbus_method_invocation_return_value(invocation, NULL); + } + + dd_close(dd); + + return; + } + + if (g_strcmp0(method_name, "DeleteElement") == 0) + { + const char *problem_id; + const char *element; + + g_variant_get(parameters, "(&s&s)", &problem_id, &element); + + struct dump_dir *dd = open_directory_for_modification_of_element( + invocation, caller_uid, problem_id, element); + if (!dd) + return; + + // TODO : move it to libreport/src/lib/dump_dir.c + char *path = concat_path_file(problem_id, element); + const int res = unlink(path); + if (res == 0 || errno == ENOENT) + g_dbus_method_invocation_return_value(invocation, NULL); + else + { + perror_msg("Can't delete an element '%s'", path); + char *error = xasprintf(_("Can't delete an element '%s' from a problem directory '%s'"), element, problem_id); + g_dbus_method_invocation_return_dbus_error(invocation, + "org.freedesktop.problems.Failure", + error); + free(error); + } + + free(path); + dd_close(dd); + + return; + } + if (g_strcmp0(method_name, "DeleteProblem") == 0) { /* Dbus parameters are always tuples. diff --git a/tests/runtests/aux/test_order b/tests/runtests/aux/test_order index e43c139..0020aad 100644 --- a/tests/runtests/aux/test_order +++ b/tests/runtests/aux/test_order @@ -24,6 +24,7 @@ dumpoops dumpxorg dbus-api dbus-NewProblem +dbus-elements-handling bodhi oops_processing
diff --git a/tests/runtests/dbus-elements-handling/PURPOSE b/tests/runtests/dbus-elements-handling/PURPOSE new file mode 100644 index 0000000..5c185a6 --- /dev/null +++ b/tests/runtests/dbus-elements-handling/PURPOSE @@ -0,0 +1,7 @@ +PURPOSE of dbus-elements-handling +Description: Check D-Bus add/remove elements and change element values methods +Author: Jakub Filak jfilak@redhat.com + +This is a test of ABRT D-Bus methods. This test is focused on adding, changing +and removing elements. The test requires two different user because the test is +focused on correct handling of priviliges for required functions. diff --git a/tests/runtests/dbus-elements-handling/runtest.sh b/tests/runtests/dbus-elements-handling/runtest.sh new file mode 100755 index 0000000..9427b89 --- /dev/null +++ b/tests/runtests/dbus-elements-handling/runtest.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# vim: dict=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# runtest.sh of dbus-elements-handling +# Description: Check D-Bus add/remove elements and change element values methods +# Author: Jakub Filak jfilak@redhat.com +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Copyright (c) 2012 Red Hat, Inc. All rights reserved. +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +. /usr/share/beakerlib/beakerlib.sh + +TEST="dbus-elements-handling" +PACKAGE="abrt" +STAT="stat --format=%A,%U,%G" + +function abrtDBusNewProblem() { + args=analyzer,libreport,executable,$(which true) + + if [ -n "$2" ]; then + args = $args,$2 + fi + + dbus-send --system --type=method_call --print-reply \ + --dest=org.freedesktop.problems /org/freedesktop/problems org.freedesktop.problems.NewProblem \ + dict:string:string:$args 2>&1 | tail -1 | sed 's/ *string *"(.*)"/\1/' +} + +function abrtDBusSetElement() { + dbus-send --system --type=method_call --print-reply \ + --dest=org.freedesktop.problems /org/freedesktop/problems org.freedesktop.problems.SetElement \ + string:$1 string:$2 string:$3 2>&1 | awk -v first=1 '{ if (first) { first=0 } else { print } } /Error/ { print }' +} + +function abrtDBusDelElement() { + dbus-send --system --type=method_call --print-reply \ + --dest=org.freedesktop.problems /org/freedesktop/problems org.freedesktop.problems.DeleteElement \ + string:$1 string:$2 2>&1 | awk -v first=1 '{ if (first) { first=0 } else { print} } /Error/ { print }' +} + +function abrtDBusGetElement() { + dbus-send --system --type=method_call --print-reply \ + --dest=org.freedesktop.problems /org/freedesktop/problems org.freedesktop.problems.GetInfo \ + string:$1 array:string:$2 2>&1 | awk -v elem=$2 -F" '/string/ { if (beg) { print $2; exit } } $0 ~ "string ""elem { beg=1 }' +} + +# $1 problem directory +# $2 user name +# $3 expected response for new +# $4 expected response for change +# $5 expected response for delete +# $6 expected response for delete nonexisting +function abrtElementsHandlingTest() { + # the time element is required, it must be there + problem_stat=`$STAT $1/time` + + rlAssertEquals "A new element is not created yet" "_$(abrtDBusGetElement $1 the_element)" "_" + res=$(su $2 -c "abrtDBusSetElement $1 the_element an_elements_value") + rlAssertEquals "SetElement method for nonexisting element" "_$res" "_$3" + if [ -n "$3" ]; then + rlAssertEquals "Failed to create the new element" "_$(abrtDBusGetElement $1 the_element)" "_" + rlAssertEquals "SetElement method for nonexisting element as root" "_$(abrtDBusSetElement $1 the_element an_elements_value)" "_" + else + rlAssertExists $1/the_element + fi + rlAssertEquals "The element has been created with a passed value" "_$(abrtDBusGetElement $1 the_element)" "_an_elements_value" + rlAssertEquals "The new element has correct rights" "_$($STAT $1/the_element)" "_$problem_stat" + + if [ ! -e $1/the_element ]; then + rlLog "Create missing element manually" + rlRun "echo -n 'the_element' > $1/the_element" 0 "Add element" + rlRun "chmod `stat --format=%a $1/time` $1/the_element" 0 "Set correct perms" + rlRun "chown `stat --format=%U:%G $1/time` $1/the_element" 0 "Set correct user and group" + fi + + res=$(su $2 -c "abrtDBusSetElement $1 the_element the_fifth_element") + rlAssertEquals "SetElement method for existing element" "_$res" "_$4" + if [ -n "$4" ]; then + rlAssertEquals "Failed to update the element" "_$(abrtDBusGetElement $1 the_element)" "_an_elements_value" + rlAssertEquals "SetElement method for existing element as root" "_$(abrtDBusSetElement $1 the_element the_fifth_element)" "_" + fi + rlAssertEquals "A value of the updated element has the_element" "_$(abrtDBusGetElement $1 the_element)" "_the_fifth_element" + rlAssertEquals "The updated element has correct rights" "_$($STAT $1/the_element)" "_$problem_stat" + + res=$(su $2 -c "abrtDBusDelElement $1 the_element") + rlAssertEquals "DelElement method for existing element" "_$res" "_$5" + if [ -n "$5" ]; then + rlAssertEquals "Failed to delete the element" "_$(abrtDBusGetElement $1 the_element)" "_the_fifth_element" + rlAssertEquals "DelElement method for existing element as root" "_$(abrtDBusDelElement $1 the_element)" "_" + else + rlAssertNotExists $1/the_element + fi + res=$(su $2 -c "abrtDBusDelElement $1 the_element") + rlAssertEquals "DelElement method for nonexisting element" "_$res" "_$6" + + old_uid=$(abrtDBusGetElement $1 uid) + res=$(su $2 -c "abrtDBusDelElement $1 uid") + rlAssertEquals "DelElement method for UID" "_$res" "_Error org.freedesktop.problems.ProtectedElement: 'uid' element can't be modified" + rlAssertEquals "The UID element is unchanged after delete attempt" "_$(abrtDBusGetElement $1 uid)" "_$old_uid" + + new_uid=$(id -u abrtdbustestanother) + res=$(su $2 -c "abrtDBusSetElement $1 uid $new_uid") + rlAssertEquals "SetElement method for UID element" "_$res" "_Error org.freedesktop.problems.ProtectedElement: 'uid' element can't be modified" + rlAssertEquals "The UID element is unchanged after set attempt" "_$(abrtDBusGetElement $1 uid)" "_$old_uid" + + old_time=$(abrtDBusGetElement $1 time) + res=$(su $2 -c "abrtDBusDelElement $1 time") + rlAssertEquals "DelElement method for TIME" "_$res" "_Error org.freedesktop.problems.ProtectedElement: 'time' element can't be modified" + rlAssertEquals "The TIME element is unchanged after delete attempt" "_$(abrtDBusGetElement $1 time)" "_$old_time" + + new_time=$((old_time + 10)) + res=$(su $2 -c "abrtDBusSetElement $1 time $new_time") + rlAssertEquals "SetElement method for TIME element" "_$res" "_Error org.freedesktop.problems.ProtectedElement: 'time' element can't be modified" + rlAssertEquals "The TIME element is unchanged after set attempt" "_$(abrtDBusGetElement $1 time)" "_$old_time" +} + +function abrtMaxCrashReportsSizeTest() { + resp=`su $2 -c "python <<EOF +import dbus +import sys +proxy=dbus.SystemBus().get_object('org.freedesktop.problems', '/org/freedesktop/problems') +iface=dbus.Interface(proxy, 'org.freedesktop.problems') +try: + iface.SetElement("$1", "onemibofx", 1024*1024*"x") +except dbus.exceptions.DBusException as e: + print "%s: %s" % (e.get_dbus_name(), e.get_dbus_message()) +EOF"` + rlAssertEquals "No free space detected" "_$resp" "_org.freedesktop.problems.Failure: No problem space left" + + resp=`su $2 -c "python <<EOF +import dbus +import sys +proxy=dbus.SystemBus().get_object('org.freedesktop.problems', '/org/freedesktop/problems') +iface=dbus.Interface(proxy, 'org.freedesktop.problems') +try: + iface.SetElement("$1", "onemibofx", 512*1024*"x") + iface.SetElement("$1", "onemibofx", 512*1024*"x") +except dbus.exceptions.DBusException as e: + print "%s: %s" % (e.get_dbus_name(), e.get_dbus_message()) +EOF"` + rlAssertEquals "Size limit correctly checks the size limit according to a new size of an element" "_$resp" "_" +} + +rlJournalStart + rlPhaseStartSetup + rlRun "useradd -c "dbus-elements-handling test an unprivileged user" -M abrtdbustest" 0 "Create a test user" + rlRun "useradd -c "dbus-elements-handling test an another user" -M abrtdbustestanother" 0 "Create an another test user" + export -f abrtDBusNewProblem + export -f abrtDBusSetElement + export -f abrtDBusDelElement + export -f abrtDBusGetElement + DUMP_LOCATION=`mktemp -d` + # Set limit to 1Midk + rlRun "cp /etc/abrt/abrt.conf /etc/abrt/abrt.conf.bak" 0 "Create a backup of abrt configuration" + rlRun "sed 's,^.*MaxCrashReportsSize.*=.*$,MaxCrashReportsSize=1,' -i /etc/abrt/abrt.conf" 0 "Set limit for crash reports to 1MiB" + rlRun "sed 's,^.*DumpLocation.*=.*$,DumpLocation=$DUMP_LOCATION,' -i /etc/abrt/abrt.conf" 0 "Change the dump location" + rlRun "systemctl restart abrtd.service" 0 "Restart abrt service" + + rlLog "Create a problem data as the root user" + roots_problem=`abrtDBusNewProblem deleted,to_be_deleted,changed,to_be_changed` + sleep 5 + roots_problem_path="$(abrt-cli list -f $DUMP_LOCATION | awk -v id=$roots_problem '$0 ~ "Directory:.*"id { print $2 }')" + if [ -z "$roots_problem_path" ]; then + rlDie "Not found path" + fi + + rlLog "Create a problem data as the unprivileged user" + unprivilegeds_problem=`su abrtdbustest -c 'abrtDBusNewProblem deleted,to_be_deleted,changed,to_be_changed'` + sleep 5 + unprivilegeds_problem_path="$(abrt-cli list -f $DUMP_LOCATION | awk -v id=$unprivilegeds_problem '$0 ~ "Directory:.*"id { print $2 }')" + if [ -z "$unprivilegeds_problem_path" ]; then + rlDie "Not found path" + fi + rlPhaseEnd + + rlPhaseStartTest "Sanity tests" + rlAssertEquals "SetElement method for an invalid problem id" "_$(abrtDBusSetElement foo the_element a_value)" "_Error org.freedesktop.problems.InvalidProblemDir: 'foo' is not a valid problem directory" + rlAssertEquals "DelElement method for an invalid problem id" "_$(abrtDBusDelElement foo the_element)" "_Error org.freedesktop.problems.InvalidProblemDir: 'foo' is not a valid problem directory" + + rlAssertEquals "Can't create an element with empty name" "_$(abrtDBusSetElement $roots_problem_path '' some_value)" "_Error org.freedesktop.problems.InvalidElement: '' is not a valid element name" + rlAssertEquals "Can't create an element with too long name" "_$(abrtDBusSetElement $roots_problem_path 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' some_value)" "_Error org.freedesktop.problems.InvalidElement: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is not a valid element name" + rlPhaseEnd + + rlPhaseStartTest "Handle elements as root" + # Test functionality as the root user + rlLog "root changes root's problem" + abrtElementsHandlingTest "$roots_problem_path" "root" + + rlLog "root changes user's problem" + abrtElementsHandlingTest "$unprivilegeds_problem_path" "root" + + rlLog "Chech max crash reports size" + abrtMaxCrashReportsSizeTest "$roots_problem_path" "root" + rlPhaseEnd + + rlPhaseStartCleanup + rlRun "abrt-cli rm $roots_problem_path" 0 "Remove roots crash directory" + rlRun "abrt-cli rm $unprivilegeds_problem_path" 0 "Remove users crash directory" + rlPhaseEnd + + rlPhaseStartSetup + rlRun "systemctl stop abrtd.service" 0 "Stop abrtd before cleaning of the dump location" + rlRun "rm -rf $DUMP_LOCATION/*" 0 "Clean the dump location" + rlRun "systemctl start abrtd.service" 0 "Start abrtd after cleaning of the dump location" + + rlLog "Create a problem data as the root user" + roots_problem=`abrtDBusNewProblem` + sleep 5 + roots_problem_path="$(abrt-cli list -f $DUMP_LOCATION | awk -v id=$roots_problem '$0 ~ "Directory:.*"id { print $2 }')" + if [ -z "$roots_problem_path" ]; then + rlDie "Not found path problem path" + fi + + rlLog "Create a problem data as the unprivileged user" + unprivilegeds_problem=`su abrtdbustest -c 'abrtDBusNewProblem'` + sleep 5 + unprivilegeds_problem_path="$(abrt-cli list -f $DUMP_LOCATION | awk -v id=$unprivilegeds_problem '$0 ~ "Directory:.*"id { print $2 }')" + if [ -z "$unprivilegeds_problem_path" ]; then + rlDie "Not found path problem path" + fi + + rlLog "Create a problem data as the unprivileged user" + second_unprivilegeds_problem=`su abrtdbustest -c 'abrtDBusNewProblem'` + sleep 5 + second_unprivilegeds_problem_path="$(abrt-cli list -f $DUMP_LOCATION | awk -v id=$second_unprivilegeds_problem '$0 ~ "Directory:.*"id { print $2 }')" + if [ -z "$second_unprivilegeds_problem_path" ]; then + rlDie "Not found path problem path" + fi + rlPhaseEnd + + rlPhaseStartTest "Handle elements as a user" + rlLog "User changes root's problem" + abrtElementsHandlingTest "$roots_problem_path" "abrtdbustest" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" + + rlLog "User changes user's problem" + abrtElementsHandlingTest "$unprivilegeds_problem_path" "abrtdbustest" + + rlLog "Another user changes user's problem" + abrtElementsHandlingTest "$second_unprivilegeds_problem_path" "abrtdbustestanother" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" \ + "Error org.freedesktop.problems.AuthFailure: Not Authorized" + + rlLog "Chech max crash reports size" + abrtMaxCrashReportsSizeTest "$unprivilegeds_problem_path" "abrtdbustest" + rlPhaseEnd + + rlPhaseStartCleanup + rlRun "userdel -r abrtdbustest" 0 "Remove the test user" + rlRun "userdel -r abrtdbustestanother" 0 "Remove the another test user" + rlRun "mv /etc/abrt/abrt.conf.bak /etc/abrt/abrt.conf" 0 "Restore abrt configuration" + rlRun "systemctl restart abrtd.service" 0 "Restart abrtd after configuration changes" + rlRun "rm -rf $DUMP_LOCATION" + rlPhaseEnd + rlJournalPrintText +rlJournalEnd