Merge lp:~jamesodhunt/upstart/remove-basic-user-sessions into lp:upstart

Proposed by James Hunt
Status: Merged
Merged at revision: 1431
Proposed branch: lp:~jamesodhunt/upstart/remove-basic-user-sessions
Merge into: lp:upstart
Diff against target: 2363 lines (+321/-1503)
14 files modified
ChangeLog (+48/-0)
dbus/Upstart.conf (+36/-6)
init/control.c (+135/-37)
init/job.c (+1/-1)
init/job_class.c (+9/-32)
init/job_process.c (+2/-91)
init/man/init.5 (+16/-53)
init/session.c (+50/-168)
init/session.h (+12/-14)
init/state.h (+3/-3)
init/tests/test_conf.c (+2/-0)
init/tests/test_state.c (+4/-7)
util/tests/test_initctl.c (+3/-0)
util/tests/test_user_sessions.sh (+0/-1091)
To merge this branch: bzr merge lp:~jamesodhunt/upstart/remove-basic-user-sessions
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Steve Langasek Needs Fixing
Review via email: mp+144873@code.launchpad.net

Description of the change

= Removal of Simple Sessions =

This branch removes the existing 'User Jobs' code since we are in the process of replacing that facility entirely by 'User Sessions' [1].

= D-Bus =

The branch reverts the D-Bus policy changes introduced to allow any user to manipulate any property and method (since with User Jobs, all commands were namespaced off by default.

Checks have been added to all control interfaces to disallow non-priv users from manipulating PID 1 properties and methods.

Note however that this branch does _not_ provide the same checks on job and job instance method calls (or log-priority method): we are reliant on D-Bus policy to police these for now. This is consistent with old pre-session behaviour but we should review this approach in a follow-on branch to make the internal D-Bus border checks fully consistent.

[1] - https://wiki.ubuntu.com/FoundationsTeam/Specs/RaringUpstartUserSessions

To post a comment you must log in.
Revision history for this message
Steve Langasek (vorlon) wrote :

Several places in the code, you're using this construction:

  if (control_get_origin_uid (message, &origin_uid) && origin_uid != uid) {
      // EPERM

So if the control_get_origin_uid() function fails, access is granted. This seems like a bad idea, as it means anyone who can figure out a way to break the function can get access. I think it's probably better to do:

  if (!control_get_origin_uid (message, &origin_uid) || origin_uid != uid) {
      // EPERM

Otherwise, this looks good to me.

review: Needs Fixing
1430. By James Hunt

* init/control.c: More careful uid checking.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Hi Steve,

Good catch! Fixed.

1431. By James Hunt

* init/control.c:
  - Typos.
  - Improved uid checks.
  - Replaced direct call to control_get_origin_uid() with call to new
    control_check_permission() (as early as possible) for clarity and
    to confine policy to one location.
  - control_set_log_prioity(): Added missing call to
    control_check_permission().
  - control_get_origin_uid(): Check message contents before allowing
    D-Bus calls.
* init/control.h: Added missing prototypes.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Reworked branch D-Bus permission checking to simplify code and confine policy to a single location.

Revision history for this message
Colin Watson (cjwatson) wrote :

Looks better now, though I'd add a comment to control_check_permission explaining that we rely on dbus to limit session bus access to the same user (since this is a little non-obvious if you happen to be just looking at that function).

review: Approve
1432. By James Hunt

* init/control.c: Comments.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Fixed. Thanks for reviewing.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ChangeLog'
--- ChangeLog 2013-01-24 08:37:53 +0000
+++ ChangeLog 2013-01-30 16:15:24 +0000
@@ -1,3 +1,51 @@
12013-01-30 James Hunt <james.hunt@ubuntu.com>
2
3 * init/control.c:
4 - Typos.
5 - Improved uid checks.
6 - Replaced direct call to control_get_origin_uid() with call to new
7 control_check_permission() (as early as possible) for clarity and
8 to confine policy to one location.
9 - control_set_log_prioity(): Added missing call to
10 control_check_permission().
11 - control_get_origin_uid(): Check message contents before allowing
12 D-Bus calls.
13
142013-01-29 James Hunt <james.hunt@ubuntu.com>
15
16 * init/control.c: More careful uid checking.
17
182013-01-25 James Hunt <james.hunt@ubuntu.com>
19
20 * init/control.c:
21 - control_reload_configuration(): Added missing permission checks.
22 - control_emit_event_with_file(): Added missing permission checks.
23 - Added calls to new function control_get_origin_uid() to allow
24 D-Bus methods to be policed by filtering on uid, rather than
25 relying on the same information that used to be stored in the
26 old Session object.
27 * init/job.c: Typo.
28 * init/job_class.c:
29 - job_class_new(): Removed user elements from session code.
30 - job_class_serialise(): Comments.
31 - job_class_deserialise(): Comments.
32 * init/job_process.c: job_process_spawn(): Removed user session
33 handling.
34 * init/session.c: Removed user session handling since it is
35 about to be replaced by the ability to run Upstart as a
36 non-privileged user (aka a 'Session Init').
37 * init/session.h: Updated prototypes.
38 * init/state.h: Comments.
39 * init/tests/test_conf.c: test_source_reload_job_dir(): Added missing
40 check.
41 * init/tests/test_state.c:
42 - session_diff(): Removed user check.
43 - Updated all calls to session_new().
44 * util/tests/test_initctl.c: test_notify_disk_writeable(): Ensure this
45 test is not run as root.
46 * util/tests/test_user_sessions.sh: Removed.
47 * init/man/init.5: Updated to reflect removal of user jobs.
48
12013-01-21 Dmitrijs Ledkovs <xnox@ubuntu.com>492013-01-21 Dmitrijs Ledkovs <xnox@ubuntu.com>
250
3 * init/xdg.[ch]: add xdg_get_cache_home and get_user_log_dir51 * init/xdg.[ch]: add xdg_get_cache_home and get_user_log_dir
452
=== modified file 'dbus/Upstart.conf'
--- dbus/Upstart.conf 2010-12-10 03:02:57 +0000
+++ dbus/Upstart.conf 2013-01-30 16:15:24 +0000
@@ -9,14 +9,12 @@
9 <allow own="com.ubuntu.Upstart" />9 <allow own="com.ubuntu.Upstart" />
10 </policy>10 </policy>
1111
12 <!-- Allow any user to invoke all of the methods on Upstart, its jobs12 <!-- Permit the root user to invoke all of the methods on Upstart, its jobs
13 or their instances, and to get and set properties - since Upstart13 or their instances, and to get and set properties. -->
14 isolates commands by user. -->14 <policy user="root">
15 <policy context="default">
16 <allow send_destination="com.ubuntu.Upstart"
17 send_interface="org.freedesktop.DBus.Introspectable" />
18 <allow send_destination="com.ubuntu.Upstart"15 <allow send_destination="com.ubuntu.Upstart"
19 send_interface="org.freedesktop.DBus.Properties" />16 send_interface="org.freedesktop.DBus.Properties" />
17
20 <allow send_destination="com.ubuntu.Upstart"18 <allow send_destination="com.ubuntu.Upstart"
21 send_interface="com.ubuntu.Upstart0_6" />19 send_interface="com.ubuntu.Upstart0_6" />
22 <allow send_destination="com.ubuntu.Upstart"20 <allow send_destination="com.ubuntu.Upstart"
@@ -24,4 +22,36 @@
24 <allow send_destination="com.ubuntu.Upstart"22 <allow send_destination="com.ubuntu.Upstart"
25 send_interface="com.ubuntu.Upstart0_6.Instance" />23 send_interface="com.ubuntu.Upstart0_6.Instance" />
26 </policy>24 </policy>
25
26 <!-- Allow any user to introspect Upstart's interfaces, to obtain the
27 values of properties (but not set them) and to invoke selected
28 methods on Upstart and its jobs that are used to walk information. -->
29 <policy context="default">
30 <allow send_destination="com.ubuntu.Upstart"
31 send_interface="org.freedesktop.DBus.Introspectable" />
32
33 <allow send_destination="com.ubuntu.Upstart"
34 send_interface="org.freedesktop.DBus.Properties"
35 send_type="method_call" send_member="Get" />
36 <allow send_destination="com.ubuntu.Upstart"
37 send_interface="org.freedesktop.DBus.Properties"
38 send_type="method_call" send_member="GetAll" />
39
40 <allow send_destination="com.ubuntu.Upstart"
41 send_interface="com.ubuntu.Upstart0_6"
42 send_type="method_call" send_member="GetJobByName" />
43 <allow send_destination="com.ubuntu.Upstart"
44 send_interface="com.ubuntu.Upstart0_6"
45 send_type="method_call" send_member="GetAllJobs" />
46
47 <allow send_destination="com.ubuntu.Upstart"
48 send_interface="com.ubuntu.Upstart0_6.Job"
49 send_type="method_call" send_member="GetInstance" />
50 <allow send_destination="com.ubuntu.Upstart"
51 send_interface="com.ubuntu.Upstart0_6.Job"
52 send_type="method_call" send_member="GetInstanceByName" />
53 <allow send_destination="com.ubuntu.Upstart"
54 send_interface="com.ubuntu.Upstart0_6.Job"
55 send_type="method_call" send_member="GetAllInstances" />
56 </policy>
27</busconfig>57</busconfig>
2858
=== modified file 'init/control.c'
--- init/control.c 2013-01-23 12:56:00 +0000
+++ init/control.c 2013-01-30 16:15:24 +0000
@@ -2,7 +2,7 @@
2 *2 *
3 * control.c - D-Bus connections, objects and methods3 * control.c - D-Bus connections, objects and methods
4 *4 *
5 * Copyright © 2009-2011 Canonical Ltd.5 * Copyright 2009-2011 Canonical Ltd.
6 * Author: Scott James Remnant <scott@netsplit.com>.6 * Author: Scott James Remnant <scott@netsplit.com>.
7 *7 *
8 * This program is free software; you can redistribute it and/or modify8 * This program is free software; you can redistribute it and/or modify
@@ -60,11 +60,15 @@
60#include "com.ubuntu.Upstart.h"60#include "com.ubuntu.Upstart.h"
6161
62/* Prototypes for static functions */62/* Prototypes for static functions */
63static int control_server_connect (DBusServer *server, DBusConnection *conn);63static int control_server_connect (DBusServer *server, DBusConnection *conn);
64static void control_disconnected (DBusConnection *conn);64static void control_disconnected (DBusConnection *conn);
65static void control_register_all (DBusConnection *conn);65static void control_register_all (DBusConnection *conn);
6666
67static void control_bus_flush (void);67static void control_bus_flush (void);
68static int control_get_origin_uid (NihDBusMessage *message, uid_t *uid)
69 __attribute__ ((warn_unused_result));
70static int control_check_permission (NihDBusMessage *message)
71 __attribute__ ((warn_unused_result));
6872
69/**73/**
70 * use_session_bus:74 * use_session_bus:
@@ -378,6 +382,8 @@
378 * Called to request that Upstart reloads its configuration from disk,382 * Called to request that Upstart reloads its configuration from disk,
379 * useful when inotify is not available or the user is generally paranoid.383 * useful when inotify is not available or the user is generally paranoid.
380 *384 *
385 * Notes: chroot sessions are permitted to make this call.
386 *
381 * Returns: zero on success, negative value on raised error.387 * Returns: zero on success, negative value on raised error.
382 **/388 **/
383int389int
@@ -386,6 +392,13 @@
386{392{
387 nih_assert (message != NULL);393 nih_assert (message != NULL);
388394
395 if (! control_check_permission (message)) {
396 nih_dbus_error_raise_printf (
397 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
398 _("You do not have permission to reload configuration"));
399 return -1;
400 }
401
389 nih_info (_("Reloading configuration"));402 nih_info (_("Reloading configuration"));
390403
391 /* This can only be called after deserialisation */404 /* This can only be called after deserialisation */
@@ -574,13 +587,20 @@
574 int wait,587 int wait,
575 int file)588 int file)
576{589{
577 Event *event;590 Event *event;
578 Blocked *blocked;591 Blocked *blocked;
579592
580 nih_assert (message != NULL);593 nih_assert (message != NULL);
581 nih_assert (name != NULL);594 nih_assert (name != NULL);
582 nih_assert (env != NULL);595 nih_assert (env != NULL);
583596
597 if (! control_check_permission (message)) {
598 nih_dbus_error_raise_printf (
599 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
600 _("You do not have permission to emit an event"));
601 return -1;
602 }
603
584 /* Verify that the name is valid */604 /* Verify that the name is valid */
585 if (! strlen (name)) {605 if (! strlen (name)) {
586 nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,606 nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
@@ -741,6 +761,13 @@
741 nih_assert (message != NULL);761 nih_assert (message != NULL);
742 nih_assert (log_priority != NULL);762 nih_assert (log_priority != NULL);
743763
764 if (! control_check_permission (message)) {
765 nih_dbus_error_raise_printf (
766 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
767 _("You do not have permission to set log priority"));
768 return -1;
769 }
770
744 if (! strcmp (log_priority, "debug")) {771 if (! strcmp (log_priority, "debug")) {
745 nih_log_set_priority (NIH_LOG_DEBUG);772 nih_log_set_priority (NIH_LOG_DEBUG);
746773
@@ -793,6 +820,11 @@
793 * Called to flush the job logs for all jobs that ended before the log820 * Called to flush the job logs for all jobs that ended before the log
794 * disk became writeable.821 * disk became writeable.
795 *822 *
823 * Notes: Session Inits are permitted to make this call. In the common
824 * case of starting a Session Init as a child of a Display Manager this
825 * is somewhat meaningless, but it does mean that if a Session Init were
826 * started from a system job, behaviour would be as expected.
827 *
796 * Returns: zero on success, negative value on raised error.828 * Returns: zero on success, negative value on raised error.
797 **/829 **/
798int830int
@@ -804,16 +836,16 @@
804836
805 nih_assert (message != NULL);837 nih_assert (message != NULL);
806838
807 /* Get the relevant session */839 if (! control_check_permission (message)) {
808 session = session_from_dbus (NULL, message);
809
810 if (session && session->user) {
811 nih_dbus_error_raise_printf (840 nih_dbus_error_raise_printf (
812 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",841 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
813 _("You do not have permission to notify disk is writeable"));842 _("You do not have permission to notify disk is writeable"));
814 return -1;843 return -1;
815 }844 }
816845
846 /* Get the relevant session */
847 session = session_from_dbus (NULL, message);
848
817 /* "nop" when run from a chroot */849 /* "nop" when run from a chroot */
818 if (session && session->chroot)850 if (session && session->chroot)
819 return 0;851 return 0;
@@ -978,13 +1010,17 @@
978 char **state)1010 char **state)
979{1011{
980 Session *session;1012 Session *session;
981 uid_t uid;
982 size_t len;1013 size_t len;
9831014
984 nih_assert (message);1015 nih_assert (message);
985 nih_assert (state);1016 nih_assert (state);
9861017
987 uid = getuid ();1018 if (! control_check_permission (message)) {
1019 nih_dbus_error_raise_printf (
1020 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
1021 _("You do not have permission to request state"));
1022 return -1;
1023 }
9881024
989 /* Get the relevant session */1025 /* Get the relevant session */
990 session = session_from_dbus (NULL, message);1026 session = session_from_dbus (NULL, message);
@@ -999,17 +1035,6 @@
999 return 0;1035 return 0;
1000 }1036 }
10011037
1002 /* Disallow users from obtaining state details, unless they
1003 * happen to own this process (which they may do in the test
1004 * scenario and when running Upstart as a non-privileged user).
1005 */
1006 if (session && session->user != uid) {
1007 nih_dbus_error_raise_printf (
1008 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
1009 _("You do not have permission to request state"));
1010 return -1;
1011 }
1012
1013 if (state_to_string (state, &len) < 0)1038 if (state_to_string (state, &len) < 0)
1014 goto error;1039 goto error;
10151040
@@ -1041,11 +1066,15 @@
1041 NihDBusMessage *message)1066 NihDBusMessage *message)
1042{1067{
1043 Session *session;1068 Session *session;
1044 uid_t uid;
10451069
1046 nih_assert (message != NULL);1070 nih_assert (message != NULL);
10471071
1048 uid = getuid ();1072 if (! control_check_permission (message)) {
1073 nih_dbus_error_raise_printf (
1074 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
1075 _("You do not have permission to request restart"));
1076 return -1;
1077 }
10491078
1050 /* Get the relevant session */1079 /* Get the relevant session */
1051 session = session_from_dbus (NULL, message);1080 session = session_from_dbus (NULL, message);
@@ -1061,17 +1090,6 @@
1061 return 0;1090 return 0;
1062 }1091 }
10631092
1064 /* Disallow users from restarting Upstart, unless they happen to
1065 * own this process (which they may do in the test scenario and
1066 * when running Upstart as a non-privileged user).
1067 */
1068 if (session && session->user != uid) {
1069 nih_dbus_error_raise_printf (
1070 DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
1071 _("You do not have permission to request restart"));
1072 return -1;
1073 }
1074
1075 nih_info (_("Restarting"));1093 nih_info (_("Restarting"));
10761094
1077 stateful_reexec ();1095 stateful_reexec ();
@@ -1117,3 +1135,83 @@
1117 NIH_ZERO (control_emit_restarted (conn, DBUS_PATH_UPSTART));1135 NIH_ZERO (control_emit_restarted (conn, DBUS_PATH_UPSTART));
1118 }1136 }
1119}1137}
1138
1139/**
1140 * control_get_origin_uid:
1141 * @message: D-Bus connection and message received,
1142 * @uid: returned uid value.
1143 *
1144 * Returns TRUE: if @uid now contains uid corresponding to @message,
1145 * else FALSE.
1146 **/
1147static int
1148control_get_origin_uid (NihDBusMessage *message, uid_t *uid)
1149{
1150 DBusError dbus_error;
1151 unsigned long unix_user = 0;
1152 const char *sender;
1153
1154 nih_assert (message);
1155 nih_assert (uid);
1156
1157 dbus_error_init (&dbus_error);
1158
1159 if (! message->message || ! message->connection)
1160 return FALSE;
1161
1162 sender = dbus_message_get_sender (message->message);
1163 if (sender) {
1164 unix_user = dbus_bus_get_unix_user (message->connection, sender,
1165 &dbus_error);
1166 if (unix_user == (unsigned long)-1) {
1167 dbus_error_free (&dbus_error);
1168 return FALSE;
1169 }
1170 } else {
1171 if (! dbus_connection_get_unix_user (message->connection,
1172 &unix_user)) {
1173 return FALSE;
1174 }
1175 }
1176
1177 *uid = (uid_t)unix_user;
1178
1179 return TRUE;
1180}
1181
1182/**
1183 * control_check_permission:
1184 *
1185 * @message: D-Bus connection and message received.
1186 *
1187 * Determine if caller should be allowed to make a control request.
1188 *
1189 * Note that these permission checks rely on D-Bus to limit
1190 * session bus access to the same user.
1191 *
1192 * Returns: TRUE if permission is granted, else FALSE.
1193 **/
1194static int
1195control_check_permission (NihDBusMessage *message)
1196{
1197 int ret;
1198 uid_t uid;
1199 pid_t pid;
1200 uid_t origin_uid = 0;
1201
1202 nih_assert (message);
1203
1204 uid = getuid ();
1205 pid = getpid ();
1206
1207 ret = control_get_origin_uid (message, &origin_uid);
1208
1209 /* Its possible that D-Bus might be unable to determine the user
1210 * making the request. In this case, deny the request unless
1211 * we're running as a Session Init or via the test harness.
1212 */
1213 if ((ret && origin_uid == uid) || user_mode || (uid && pid != 1))
1214 return TRUE;
1215
1216 return FALSE;
1217}
11201218
=== modified file 'init/job.c'
--- init/job.c 2012-11-07 11:56:33 +0000
+++ init/job.c 2013-01-30 16:15:24 +0000
@@ -1701,7 +1701,7 @@
1701/**1701/**
1702 * job_serialise_all:1702 * job_serialise_all:
1703 *1703 *
1704 * Convert existing Session objects to JSON representation.1704 * Convert existing Job objects to JSON representation.
1705 *1705 *
1706 * Returns: JSON object containing array of Job objects, or NULL on error.1706 * Returns: JSON object containing array of Job objects, or NULL on error.
1707 **/1707 **/
17081708
=== modified file 'init/job_class.c'
--- init/job_class.c 2012-12-14 20:55:41 +0000
+++ init/job_class.c 2013-01-30 16:15:24 +0000
@@ -142,40 +142,17 @@
142 goto error;142 goto error;
143143
144 class->session = session;144 class->session = session;
145 if (class->session145
146 && class->session->chroot146 if (class->session && class->session->chroot) {
147 && class->session->user) {
148 nih_local char *uid = NULL;
149
150 uid = nih_sprintf (NULL, "%d", class->session->user);
151 if (! uid)
152 goto error;
153
154 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
155 session->chroot, uid,
156 class->name, NULL);
157
158 } else if (class->session
159 && class->session->chroot) {
160 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",147 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
161 session->chroot,148 session->chroot,
162 class->name, NULL);149 class->name, NULL);
163150
164 } else if (class->session
165 && class->session->user) {
166 nih_local char *uid = NULL;
167
168 uid = nih_sprintf (NULL, "%d", class->session->user);
169 if (! uid)
170 goto error;
171
172 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
173 uid, class->name, NULL);
174
175 } else {151 } else {
176 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",152 class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
177 class->name, NULL);153 class->name, NULL);
178 }154 }
155
179 if (! class->path)156 if (! class->path)
180 goto error;157 goto error;
181158
@@ -1595,12 +1572,12 @@
1595 if (! json)1572 if (! json)
1596 return NULL;1573 return NULL;
1597 1574
1598 /* XXX: user and chroot jobs are not currently supported1575 /* XXX: chroot jobs are not currently supported
1599 * due to ConfSources not currently being serialised.1576 * due to ConfSources not currently being serialised.
1600 */1577 */
1601 if (class->session) {1578 if (class->session) {
1602 nih_info ("WARNING: serialisation of user jobs and "1579 nih_info ("WARNING: serialisation of chroot "
1603 "chroot sessions not currently supported");1580 "sessions not currently supported");
1604 goto error;1581 goto error;
1605 }1582 }
16061583
@@ -1828,12 +1805,12 @@
18281805
1829 session = session_from_index (session_index);1806 session = session_from_index (session_index);
18301807
1831 /* XXX: user and chroot jobs are not currently supported1808 /* XXX: chroot jobs are not currently supported
1832 * due to ConfSources not currently being serialised.1809 * due to ConfSources not currently being serialised.
1833 */1810 */
1834 if (session) {1811 if (session) {
1835 nih_info ("WARNING: deserialisation of user jobs and "1812 nih_info ("WARNING: deserialisation of chroot "
1836 "chroot sessions not currently supported");1813 "sessions not currently supported");
1837 goto error;1814 goto error;
1838 }1815 }
18391816
18401817
=== modified file 'init/job_process.c'
--- init/job_process.c 2013-01-23 12:40:36 +0000
+++ init/job_process.c 2013-01-30 16:15:24 +0000
@@ -420,8 +420,6 @@
420 char pts_name[PATH_MAX];420 char pts_name[PATH_MAX];
421 char filename[PATH_MAX];421 char filename[PATH_MAX];
422 FILE *fd;422 FILE *fd;
423 int user_job = FALSE;
424 nih_local char *user_dir = NULL;
425 nih_local char *log_path = NULL;423 nih_local char *log_path = NULL;
426 JobClass *class;424 JobClass *class;
427 uid_t job_setuid = -1;425 uid_t job_setuid = -1;
@@ -439,20 +437,14 @@
439437
440 nih_assert (class != NULL);438 nih_assert (class != NULL);
441439
442 if (class && class->session && class->session->user)
443 user_job = TRUE;
444
445 /* Create a pipe to communicate with the child process until it440 /* Create a pipe to communicate with the child process until it
446 * execs so we know whether that was successful or an error occurred.441 * execs so we know whether that was successful or an error occurred.
447 */442 */
448 if (pipe (fds) < 0)443 if (pipe (fds) < 0)
449 nih_return_system_error (-1);444 nih_return_system_error (-1);
450445
451 /* Logging of user job output is not currently possible */446 if (class->console == CONSOLE_LOG && disable_job_logging)
452 if (class->console == CONSOLE_LOG) {
453 if (disable_job_logging || user_job)
454 class->console = CONSOLE_NONE;447 class->console = CONSOLE_NONE;
455 }
456448
457 if (class->console == CONSOLE_LOG) {449 if (class->console == CONSOLE_LOG) {
458 NihError *err;450 NihError *err;
@@ -655,87 +647,6 @@
655 /* Set the process environment from the function parameters. */647 /* Set the process environment from the function parameters. */
656 environ = (char **)env;648 environ = (char **)env;
657649
658 /* Handle unprivileged user job by dropping privileges to
659 * their level as soon as possible to avoid privilege
660 * escalations when we set resource limits.
661 */
662 if (user_job) {
663 uid_t uid = class->session->user;
664 struct passwd *pw = NULL;
665
666 /* D-Bus does not expose a public API call to allow
667 * us to query a users primary group.
668 * _dbus_user_info_fill_uid () seems to exist for this
669 * purpose, but is a "secret" API. It is unclear why
670 * D-Bus neglects the gid when it allows the uid
671 * to be queried directly.
672 *
673 * Our only recourse is to disallow user sessions in a
674 * chroot and assume that all other user sessions
675 * originate from the local system. In this way, we can
676 * bypass D-Bus and use getpwuid ().
677 */
678
679 if (class->session->chroot) {
680 /* We cannot determine the group id of the user
681 * session in the chroot via D-Bus, so disallow
682 * all jobs in such an environment.
683 */
684 nih_error_raise (EPERM, "user jobs not supported in chroots");
685 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHROOT, 0);
686 }
687
688 pw = getpwuid (uid);
689
690 if (!pw) {
691 nih_error_raise_system ();
692 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0);
693 }
694
695 nih_assert (pw->pw_uid == uid);
696
697 if (! pw->pw_dir) {
698 nih_error_raise_printf (ENOENT,
699 "no home directory for user with uid %d", uid);
700 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0);
701
702 }
703
704 /* Note we don't use NIH_MUST since this could result in a
705 * DoS for a (low priority) user job in low-memory scenarios.
706 */
707 user_dir = nih_strdup (NULL, pw->pw_dir);
708
709 if (! user_dir) {
710 nih_error_raise_no_memory ();
711 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_ALLOC, 0);
712 }
713
714 /* Ensure the file associated with fd 9
715 * (/proc/self/fd/9) is owned by the user we're about to
716 * become to avoid EPERM.
717 */
718 if (script_fd != -1 && fchown (script_fd, pw->pw_uid, pw->pw_gid) < 0) {
719 nih_error_raise_system ();
720 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHOWN, 0);
721 }
722
723 if (geteuid () == 0 && initgroups (pw->pw_name, pw->pw_gid) < 0) {
724 nih_error_raise_system ();
725 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_INITGROUPS, 0);
726 }
727
728 if (pw->pw_gid && setgid (pw->pw_gid) < 0) {
729 nih_error_raise_system ();
730 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETGID, 0);
731 }
732
733 if (pw->pw_uid && setuid (pw->pw_uid) < 0) {
734 nih_error_raise_system ();
735 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETUID, 0);
736 }
737 }
738
739 /* Set the standard file descriptors to an output of our chosing;650 /* Set the standard file descriptors to an output of our chosing;
740 * any other open descriptor must be intended for the child, or have651 * any other open descriptor must be intended for the child, or have
741 * the FD_CLOEXEC flag so it's automatically closed when we exec()652 * the FD_CLOEXEC flag so it's automatically closed when we exec()
@@ -854,7 +765,7 @@
854 * configured in the job, or to the root directory of the filesystem765 * configured in the job, or to the root directory of the filesystem
855 * (or at least relative to the chroot).766 * (or at least relative to the chroot).
856 */767 */
857 if (chdir (class->chdir ? class->chdir : user_job ? user_dir : "/") < 0) {768 if (chdir (class->chdir ? class->chdir : "/") < 0) {
858 nih_error_raise_system ();769 nih_error_raise_system ();
859 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0);770 job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0);
860 }771 }
861772
=== modified file 'init/man/init.5'
--- init/man/init.5 2013-01-23 12:40:36 +0000
+++ init/man/init.5 2013-01-30 16:15:24 +0000
@@ -1,4 +1,4 @@
1.TH init 5 2012-12-18 "Upstart"1.TH init 5 2013-01-25 "Upstart"
2.\"2.\"
3.SH NAME3.SH NAME
4init \- Upstart init daemon job configuration4init \- Upstart init daemon job configuration
@@ -9,13 +9,14 @@
9Default location of system job configuration files.9Default location of system job configuration files.
10.\"10.\"
11.TP11.TP
12.B $HOME/.init/
13Default location of user job configuration files.
14.\"
15.TP
16.B $XDG_CONFIG_HOME/upstart/, $XDG_CONFIG_DIRS/upstart/12.B $XDG_CONFIG_HOME/upstart/, $XDG_CONFIG_DIRS/upstart/
17Default locations of user session job configuration files.13Default locations of user session job configuration files.
18.\"14.\"
15.TP
16.B $HOME/.init/
17Deprecated location of user job configuration files (still
18honoured by User Session Mode).
19.\"
19.SH DESCRIPTION20.SH DESCRIPTION
20On startup, the Upstart21On startup, the Upstart
21.BR init (8)22.BR init (8)
@@ -24,11 +25,6 @@
24directory, and watches for future changes to these files using25directory, and watches for future changes to these files using
25.BR inotify (7).26.BR inotify (7).
2627
27If D\-Bus has been configured to allow non\-privileged users to invoke all
28Upstart D\-Bus methods, Upstart is also able to manage User Jobs. See
29.B User Jobs
30for further details.
31
32If Upstart was invoked as a user process with \-\-user option, it will28If Upstart was invoked as a user process with \-\-user option, it will
33run in User Session mode. See29run in User Session mode. See
34.B User Session Mode30.B User Session Mode
@@ -85,33 +81,6 @@
8581
86Configuration files are plain text and should not be executable.82Configuration files are plain text and should not be executable.
87.\"83.\"
88.SS User Jobs
89
90A User Job is a job configuration file created by a non\-privileged user
91in their
92.B $HOME/.init/
93directory. Job configuration files in this directory have the same
94syntax as system job configuration files.
95Files in this directory will be read and an
96.BR inotify (7)
97watch created the first time a user runs
98.BR initctl (8) "."
99
100Any user can create user jobs, but that user can control
101.I only
102jobs they create.
103
104Users are able to manage their jobs using the standard
105.BR initctl (8)
106facility.
107
108Note that stanzas which manipulate resources limits may cause a job to
109fail to start should the value provided to such a stanza attempt to
110exceed the maximum value the users privilege level allows.
111
112Note that a user job configuration file cannot have the same name as a
113system job configuration file.
114.\"
115.SS Chroot Support84.SS Chroot Support
11685
117Upstart is able to manage jobs within a \fBchroot\fP(2). To control jobs86Upstart is able to manage jobs within a \fBchroot\fP(2). To control jobs
@@ -124,10 +93,6 @@
124.B Process environment93.B Process environment
125below).94below).
12695
127Note that User Jobs
128.B cannot
129be used within a chroot environment.
130
131.\"96.\"
132.SS User Session Mode97.SS User Session Mode
13398
@@ -447,9 +412,10 @@
447.BR initctl (8)412.BR initctl (8)
448utility to default to acting on the job the commands are called from.413utility to default to acting on the job the commands are called from.
449414
450When running in a user session, the additional UPSTART_SESSION environment415When running in a user session, the additional
451variable is also set and contains the DBus path to the private bus used by416.B UPSTART_SESSION
452the upstart instance daemon.417environment variable is also set and contains the DBus path to the
418private bus used by the upstart instance daemon.
453419
454.TP420.TP
455.B env \fIKEY\fR[=\fIVALUE\fR]421.B env \fIKEY\fR[=\fIVALUE\fR]
@@ -667,12 +633,7 @@
667.B log633.B log
668.RS634.RS
669.B635.B
670Only applies to system and user session jobs:636If \fBlog\fR is specified, standard input is connected
671if specified by user jobs, the job will be considered to have specified
672the value
673.BR none "."
674
675For system and user session jobs jobs, if \fBlog\fR is specified, standard input is connected
676to637to
677.IR /dev/null ","638.IR /dev/null ","
678and standard output and standard error are connected to a pseudo-tty639and standard output and standard error are connected to a pseudo-tty
@@ -881,7 +842,7 @@
881block is used for all job processes. If both stanzas are unspecified,842block is used for all job processes. If both stanzas are unspecified,
882all job processes will run with its group ID set to 0 in the case of843all job processes will run with its group ID set to 0 in the case of
883system jobs, and as the primary group of the user in the case of User844system jobs, and as the primary group of the user in the case of User
884Jobs.845Session jobs.
885.\"846.\"
886.SS Override File Handling847.SS Override File Handling
887Override files allow a jobs environment to be changed without modifying848Override files allow a jobs environment to be changed without modifying
@@ -1025,11 +986,13 @@
1025.986.
1026.TP987.TP
1027.I $HOME/.init/*.conf988.I $HOME/.init/*.conf
1028User job configuration files.989User job configuration files
990.BR (deprecated) .
1029.991.
1030.TP992.TP
1031.I $HOME/.init/*.override993.I $HOME/.init/*.override
1032User job override files.994User job override files.
995.BR (deprecated) .
1033.996.
1034.TP997.TP
1035.I $XDG_CONFIG_HOME/upstart/*.conf998.I $XDG_CONFIG_HOME/upstart/*.conf
@@ -1063,7 +1026,7 @@
1063.RB < https://launchpad.net/upstart/+bugs >1026.RB < https://launchpad.net/upstart/+bugs >
1064.\"1027.\"
1065.SH COPYRIGHT1028.SH COPYRIGHT
1066Copyright \(co 2009-2011 Canonical Ltd.1029Copyright \(co 2009-2013 Canonical Ltd.
1067.br1030.br
1068This is free software; see the source for copying conditions. There is NO1031This is free software; see the source for copying conditions. There is NO
1069warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.1032warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10701033
=== modified file 'init/session.c'
--- init/session.c 2012-11-14 14:47:19 +0000
+++ init/session.c 2013-01-30 16:15:24 +0000
@@ -68,7 +68,7 @@
68/**68/**
69 * disable_sessions:69 * disable_sessions:
70 *70 *
71 * If TRUE, disable user and chroot sessions, resulting in a71 * If TRUE, disable chroot sessions, resulting in a
72 * "traditional" (pre-session support) system.72 * "traditional" (pre-session support) system.
73 **/73 **/
74int disable_sessions = FALSE;74int disable_sessions = FALSE;
@@ -93,21 +93,19 @@
93/**93/**
94 * session_new:94 * session_new:
95 * @parent: parent,95 * @parent: parent,
96 * @chroot: full chroot path,96 * @chroot: full chroot path.
97 * @user: user id.
98 *97 *
99 * Create a new session.98 * Create a new chroot session.
100 *99 *
101 * Returns: new Session, or NULL on error.100 * Returns: new Session, or NULL on error.
102 **/101 **/
103Session *102Session *
104session_new (const void *parent,103session_new (const void *parent,
105 const char *chroot,104 const char *chroot)
106 uid_t user)
107{105{
108 Session *session;106 Session *session;
109107
110 nih_assert ((chroot != NULL) || (user != 0));108 nih_assert (chroot);
111109
112 session_init ();110 session_init ();
113111
@@ -117,18 +115,12 @@
117115
118 nih_list_init (&session->entry);116 nih_list_init (&session->entry);
119117
120 if (chroot) {118 session->chroot = nih_strdup (session, chroot);
121 session->chroot = nih_strdup (session, chroot);119 if (! session->chroot) {
122 if (! session->chroot) {120 nih_free (session);
123 nih_free (session);121 return NULL;
124 return NULL;
125 }
126 } else {
127 session->chroot = NULL;
128 }122 }
129123
130 session->user = user;
131
132 session->conf_path = NULL;124 session->conf_path = NULL;
133125
134 nih_alloc_set_destructor (session, nih_list_destroy);126 nih_alloc_set_destructor (session, nih_list_destroy);
@@ -151,14 +143,13 @@
151session_from_dbus (const void *parent,143session_from_dbus (const void *parent,
152 NihDBusMessage *message)144 NihDBusMessage *message)
153{145{
154 const char *sender;146 DBusError dbus_error;
155 DBusError dbus_error;147 unsigned long unix_process_id;
156 unsigned long unix_user;148 char root[PATH_MAX];
157 unsigned long unix_process_id;149 Session *session;
158 char root[PATH_MAX];150 nih_local char *conf_path = NULL;
159 Session *session;151 nih_local char *symlink = NULL;
160 struct passwd *pwd;152 ssize_t len;
161 nih_local char *conf_path = NULL;
162153
163 nih_assert (message != NULL);154 nih_assert (message != NULL);
164155
@@ -170,126 +161,43 @@
170161
171 session_init ();162 session_init ();
172163
173 /* Ask D-Bus nicely for the origin uid and/or pid of the caller;
174 * sadly we can't ask the bus daemon for the origin pid, so that
175 * one will just have to stay user-session only.
176 */
177 dbus_error_init (&dbus_error);164 dbus_error_init (&dbus_error);
178165
179 sender = dbus_message_get_sender (message->message);166 /* Query origin pid of the caller */
180 if (sender) {167 if (! dbus_connection_get_unix_process_id (message->connection,
181 unix_user = dbus_bus_get_unix_user (message->connection, sender,168 &unix_process_id)) {
182 &dbus_error);169 return NULL;
183 if (unix_user == (unsigned long)-1) {170 }
184 dbus_error_free (&dbus_error);171
185 unix_user = 0;172 /* Look up the root path for retrieved pid */
186 }173 symlink = NIH_MUST (nih_sprintf (NULL, "/proc/%lu/root",
187174 unix_process_id));
188 unix_process_id = 0;175 len = readlink (symlink, root, sizeof root);
189176 if (len < 0)
190 } else {177 return NULL;
191 if (! dbus_connection_get_unix_user (message->connection,178
192 &unix_user))179 root[len] = '\0';
193 unix_process_id = 0;180
194181 /* Path is not inside a chroot */
195 if (! dbus_connection_get_unix_process_id (message->connection,182 if (! strcmp (root, "/"))
196 &unix_process_id))183 return NULL;
197 unix_process_id = 0;
198 }
199
200 /* If we retrieved a process id, look up the root path for it;
201 * if it's just '/' don't worry so much about it.
202 */
203 if (unix_process_id) {
204 nih_local char *symlink = NULL;
205 ssize_t len;
206
207 symlink = NIH_MUST (nih_sprintf (NULL, "/proc/%lu/root",
208 unix_process_id));
209 len = readlink (symlink, root, sizeof root);
210 if (len < 0)
211 return NULL;
212
213 root[len] = '\0';
214
215 if (! strcmp (root, "/")) {
216 unix_process_id = 0;
217 if (! unix_user)
218 return NULL;
219 }
220
221 } else if (! unix_user) {
222 /* No process id or user id found, return the NULL session */
223 return NULL;
224 }
225
226 if (unix_user) {
227 pwd = getpwuid (unix_user);
228
229 if (! pwd || ! pwd->pw_dir) {
230 nih_error ("%lu: %s: %s", unix_user,
231 _("Unable to lookup home directory"),
232 strerror (errno));
233 return NULL;
234 }
235
236 NIH_MUST (nih_strcat_sprintf (&conf_path, NULL, "%s/%s",
237 pwd->pw_dir, USERCONFDIR));
238 }
239
240184
241 /* Now find in the existing Sessions list */185 /* Now find in the existing Sessions list */
242 NIH_LIST_FOREACH_SAFE (sessions, iter) {186 NIH_LIST_FOREACH_SAFE (sessions, iter) {
243 Session *session = (Session *)iter;187 Session *session = (Session *)iter;
244188
245 if (unix_process_id) {189 if (strcmp (session->chroot, root))
246 if (! session->chroot)
247 continue;
248
249 /* ignore sessions relating to other chroots */190 /* ignore sessions relating to other chroots */
250 if (strcmp (session->chroot, root))
251 continue;
252 }
253
254 /* ignore sessions relating to other users */
255 if (unix_user != session->user)
256 continue;191 continue;
257192
258 /* Found a user with the same uid but different
259 * conf_path to the existing session user. Either the
260 * original user has been deleted and a new user created
261 * with the same uid, or the original users home
262 * directory has changed since they first started
263 * running jobs. Whatever the reason, we (can only) honour
264 * the new value.
265 *
266 * Since multiple users with the same uid are considered
267 * to be "the same user", invalidate the old path,
268 * allowing the correct new path to be set below.
269 *
270 * Note that there may be a possibility of trouble if
271 * the scenario relates to a deleted user and that original
272 * user still has jobs running. However, if that were the
273 * case, those jobs would likely fail anyway since they
274 * would have no working directory due to the original
275 * users home directory being deleted/changed/made inaccessible.
276 */
277 if (unix_user && conf_path && session->conf_path &&
278 strcmp (conf_path, session->conf_path)) {
279 nih_free (session->conf_path);
280 session->conf_path = NULL;
281 }
282
283 if (! session->conf_path)193 if (! session->conf_path)
284 session_create_conf_source (session, FALSE);194 session_create_conf_source (session, FALSE);
285195
286 return session;196 return session;
287 }197 }
288198
289
290 /* Didn't find one, make a new one */199 /* Didn't find one, make a new one */
291 session = NIH_MUST (session_new (parent, unix_process_id ? root : NULL,200 session = NIH_MUST (session_new (parent, root));
292 unix_user));
293 session_create_conf_source (session, FALSE);201 session_create_conf_source (session, FALSE);
294202
295 return session;203 return session;
@@ -317,27 +225,8 @@
317 session_init ();225 session_init ();
318226
319 if (! deserialised) {227 if (! deserialised) {
320 if (session->chroot)228 session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));
321 session->conf_path = NIH_MUST (nih_strdup (NULL, session->chroot));229 NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
322 if (session->user) {
323 struct passwd *pwd;
324
325 pwd = getpwuid (session->user);
326 if (! pwd) {
327 nih_error ("%d: %s: %s", session->user,
328 _("Unable to lookup home directory"),
329 strerror (errno));
330
331 nih_free (session->conf_path);
332 session->conf_path = NULL;
333 return;
334 }
335
336 NIH_MUST (nih_strcat_sprintf (&session->conf_path, NULL, "%s/%s",
337 pwd->pw_dir, USERCONFDIR));
338 } else {
339 NIH_MUST (nih_strcat (&session->conf_path, NULL, CONFDIR));
340 }
341 }230 }
342231
343 source = NIH_MUST (conf_source_new (session, session->conf_path,232 source = NIH_MUST (conf_source_new (session, session->conf_path,
@@ -375,9 +264,10 @@
375 json_object *json;264 json_object *json;
376 json_object *conf_path = NULL;265 json_object *conf_path = NULL;
377 json_object *chroot = NULL;266 json_object *chroot = NULL;
378 json_object *user;
379267
380 nih_assert (session);268 nih_assert (session);
269 nih_assert (session->chroot);
270 nih_assert (session->conf_path);
381271
382 session_init ();272 session_init ();
383273
@@ -385,26 +275,16 @@
385 if (! json)275 if (! json)
386 return NULL;276 return NULL;
387277
388 if (session->chroot) {278 chroot = json_object_new_string (session->chroot);
389 chroot = json_object_new_string (session->chroot);279 if (! chroot)
390 if (! chroot)280 goto error;
391 goto error;
392 }
393281
394 json_object_object_add (json, "chroot", chroot);282 json_object_object_add (json, "chroot", chroot);
395283
396 user = state_new_json_int (session->user);284 conf_path = json_object_new_string (session->conf_path);
397 if (! user)285 if (! conf_path)
398 goto error;286 goto error;
399287
400 json_object_object_add (json, "user", user);
401
402 if (session->conf_path) {
403 conf_path = json_object_new_string (session->conf_path);
404 if (! conf_path)
405 goto error;
406 }
407
408 json_object_object_add (json, "conf_path", conf_path);288 json_object_object_add (json, "conf_path", conf_path);
409289
410 return json;290 return json;
@@ -459,6 +339,9 @@
459 *339 *
460 * Convert @json into a Session object.340 * Convert @json into a Session object.
461 *341 *
342 * NOTE: Any user sessions seen when re-execing from an older version
343 * of Upstart are implicitly ignored.
344 *
462 * Returns: Session object, or NULL on error.345 * Returns: Session object, or NULL on error.
463 **/346 **/
464static Session *347static Session *
@@ -466,7 +349,6 @@
466{349{
467 Session *session = NULL;350 Session *session = NULL;
468 nih_local const char *chroot = NULL;351 nih_local const char *chroot = NULL;
469 uid_t user;
470352
471 nih_assert (json);353 nih_assert (json);
472354
@@ -477,11 +359,11 @@
477 if (! state_get_json_string_var (json, "chroot", NULL, chroot))359 if (! state_get_json_string_var (json, "chroot", NULL, chroot))
478 return NULL;360 return NULL;
479361
480 if (! state_get_json_int_var (json, "user", user))362 if (! chroot)
481 return NULL;363 return NULL;
482364
483 /* Create a new session */365 /* Create a new session */
484 session = NIH_MUST (session_new (NULL, chroot, user));366 session = NIH_MUST (session_new (NULL, chroot));
485367
486 if (! state_get_json_string_var_to_obj (json, session, conf_path))368 if (! state_get_json_string_var_to_obj (json, session, conf_path))
487 goto error;369 goto error;
488370
=== modified file 'init/session.h'
--- init/session.h 2012-11-22 16:32:36 +0000
+++ init/session.h 2013-01-30 16:15:24 +0000
@@ -33,14 +33,11 @@
33 * Session:33 * Session:
34 * @entry: list header,34 * @entry: list header,
35 * @chroot: path all jobs are chrooted to,35 * @chroot: path all jobs are chrooted to,
36 * @user: uid all jobs are switched to,36 * @conf_path: configuration path (full path to chroot root).
37 * @conf_path: configuration path (either full path to chroot root, or
38 * full path to users job directory (which may itself be prepended
39 * with a chroot path)).
40 *37 *
41 * This structure is used to identify collections of jobs38 * This structure is used to identify collections of jobs
42 * that share either a common @chroot and/or common @user. Note that39 * that share a common @chroot (*). Note that @conf_path is
43 * @conf_path is unique across all sessions.40 * unique across all sessions.
44 *41 *
45 * Summary of Session values for different environments:42 * Summary of Session values for different environments:
46 *43 *
@@ -50,27 +47,28 @@
50 * | user | PID | chroot | uid | Object contents |47 * | user | PID | chroot | uid | Object contents |
51 * +------+------+--------+-----+-----------------------------------+48 * +------+------+--------+-----+-----------------------------------+
52 * | 0 | >0 | no | 0 | NULL (*1) |49 * | 0 | >0 | no | 0 | NULL (*1) |
53 * | >0 | "0" | no | >0 | uid + conf_path set to "~/.init". |
54 * | 0 | >0 | yes | 0 | chroot + conf_path set |50 * | 0 | >0 | yes | 0 | chroot + conf_path set |
55 * | >0 | ?? | yes | >0 | XXX: fails (*2) |51 * | >0 | ?? | yes | >0 | Not permitted (*2) |
56 * +------+------+--------+-----+-----------------------------------+52 * +------+------+--------+-----+-----------------------------------+
57 *53 *
58 * Notes:54 * Notes:
59 *55 *
56 * (*) - this structure used to also store user session details (hence
57 * the name), but the functionality was removed with the advent of
58 * a true user mode.
59 *
60 * (*1) - The "NULL session" represents the "traditional" environment60 * (*1) - The "NULL session" represents the "traditional" environment
61 * before sessions were introduced (namely a non-chroot environment61 * before sessions were introduced (namely a non-chroot environment
62 * where all job and event operations were handled by uid 0 (root)).62 * where all job and event operations were handled by uid 0 (root)).
63 *63 *
64 * (*2) - error is:64 * (*2) - User lookup is not reliable since the user to query exists
65 * within the chroot, but the only possible lookup is outside the
66 * chroot.
65 *67 *
66 * initctl: Unable to connect to system bus: Failed to connect to socket
67 * /var/run/dbus/system_bus_socket: No such file or directory
68 *
69 **/68 **/
70typedef struct session {69typedef struct session {
71 NihList entry;70 NihList entry;
72 char * chroot;71 char * chroot;
73 uid_t user;
74 char * conf_path;72 char * conf_path;
75} Session;73} Session;
7674
@@ -81,7 +79,7 @@
8179
82void session_init (void);80void session_init (void);
8381
84Session * session_new (const void *parent, const char *chroot, uid_t user)82Session * session_new (const void *parent, const char *chroot)
85 __attribute__ ((malloc, warn_unused_result));83 __attribute__ ((malloc, warn_unused_result));
8684
87Session * session_from_dbus (const void *parent, NihDBusMessage *message);85Session * session_from_dbus (const void *parent, NihDBusMessage *message);
8886
=== modified file 'init/state.h'
--- init/state.h 2012-12-04 16:09:18 +0000
+++ init/state.h 2013-01-30 16:15:24 +0000
@@ -28,10 +28,10 @@
28 * detect that is *has* changed filesystem context.28 * detect that is *has* changed filesystem context.
29 *29 *
30 * - Since ConfSources are NOT serialised, it is currently not possible30 * - Since ConfSources are NOT serialised, it is currently not possible
31 * to support user jobs and chroot jobs (because the only ConfSource31 * to support chroot jobs (because the only ConfSource
32 * objects created are those at startup (for '/etc/init/'): any32 * objects created are those at startup (for '/etc/init/'): any
33 * pre-existing ConfSources with non-NULL sessions representing33 * pre-existing ConfSources with non-NULL Session objects will
34 * user jobs will be ignored).34 * be ignored).
35 *35 *
36 * - parent/child timeout handling: we won't support down-grading initially.36 * - parent/child timeout handling: we won't support down-grading initially.
37 *37 *
3838
=== modified file 'init/tests/test_conf.c'
--- init/tests/test_conf.c 2013-01-04 13:03:47 +0000
+++ init/tests/test_conf.c 2013-01-30 16:15:24 +0000
@@ -1404,6 +1404,8 @@
1404 fclose (f);1404 fclose (f);
14051405
1406 source = conf_source_new (NULL, dirname, CONF_JOB_DIR);1406 source = conf_source_new (NULL, dirname, CONF_JOB_DIR);
1407 TEST_NE_P (source, NULL);
1408
1407 ret = conf_source_reload (source);1409 ret = conf_source_reload (source);
14081410
1409 TEST_EQ (ret, 0);1411 TEST_EQ (ret, 0);
14101412
=== modified file 'init/tests/test_state.c'
--- init/tests/test_state.c 2012-12-07 21:38:17 +0000
+++ init/tests/test_state.c 2013-01-30 16:15:24 +0000
@@ -217,9 +217,6 @@
217 if (obj_string_check (a, b, chroot))217 if (obj_string_check (a, b, chroot))
218 goto fail;218 goto fail;
219219
220 if (obj_num_check (a, b, user))
221 goto fail;
222
223 if (obj_string_check (a, b, conf_path))220 if (obj_string_check (a, b, conf_path))
224 goto fail;221 goto fail;
225222
@@ -871,12 +868,12 @@
871 TEST_NE_P (json, NULL);868 TEST_NE_P (json, NULL);
872869
873 /* Create a couple of sessions */870 /* Create a couple of sessions */
874 session1 = session_new (NULL, "/abc", getuid ());871 session1 = session_new (NULL, "/abc");
875 TEST_NE_P (session1, NULL);872 TEST_NE_P (session1, NULL);
876 session1->conf_path = NIH_MUST (nih_strdup (session1, "/def/ghi"));873 session1->conf_path = NIH_MUST (nih_strdup (session1, "/def/ghi"));
877 TEST_LIST_NOT_EMPTY (sessions);874 TEST_LIST_NOT_EMPTY (sessions);
878875
879 session2 = session_new (NULL, "/foo", 0);876 session2 = session_new (NULL, "/foo");
880 TEST_NE_P (session2, NULL);877 TEST_NE_P (session2, NULL);
881 session2->conf_path = NIH_MUST (nih_strdup (session2, "/bar/baz"));878 session2->conf_path = NIH_MUST (nih_strdup (session2, "/bar/baz"));
882879
@@ -1264,7 +1261,7 @@
1264 TEST_LIST_EMPTY (conf_sources);1261 TEST_LIST_EMPTY (conf_sources);
1265 TEST_HASH_EMPTY (job_classes);1262 TEST_HASH_EMPTY (job_classes);
12661263
1267 session = session_new (NULL, "/my/session", getuid ());1264 session = session_new (NULL, "/my/session");
1268 TEST_NE_P (session, NULL);1265 TEST_NE_P (session, NULL);
1269 session->conf_path = NIH_MUST (nih_strdup (session, "/lives/here"));1266 session->conf_path = NIH_MUST (nih_strdup (session, "/lives/here"));
1270 TEST_LIST_NOT_EMPTY (sessions);1267 TEST_LIST_NOT_EMPTY (sessions);
@@ -1659,7 +1656,7 @@
1659 TEST_NE_P (env, NULL);1656 TEST_NE_P (env, NULL);
1660 TEST_NE_P (environ_add (&env, NULL, &len, TRUE, "FOO=BAR"), NULL);1657 TEST_NE_P (environ_add (&env, NULL, &len, TRUE, "FOO=BAR"), NULL);
16611658
1662 session = session_new (NULL, "/abc", getuid ());1659 session = session_new (NULL, "/abc");
1663 TEST_NE_P (session, NULL);1660 TEST_NE_P (session, NULL);
1664 session->conf_path = NIH_MUST (nih_strdup (session, "/def/ghi"));1661 session->conf_path = NIH_MUST (nih_strdup (session, "/def/ghi"));
1665 TEST_LIST_NOT_EMPTY (sessions);1662 TEST_LIST_NOT_EMPTY (sessions);
16661663
=== modified file 'util/tests/test_initctl.c'
--- util/tests/test_initctl.c 2013-01-22 18:17:04 +0000
+++ util/tests/test_initctl.c 2013-01-30 16:15:24 +0000
@@ -12490,6 +12490,9 @@
12490 /* Ensure again that no log file written */12490 /* Ensure again that no log file written */
12491 TEST_LT (stat (logfile_name, &statbuf), 0);12491 TEST_LT (stat (logfile_name, &statbuf), 0);
1249212492
12493 /* Must not be run as root */
12494 TEST_TRUE (getuid ());
12495
12493 cmd = nih_sprintf (NULL, "%s notify-disk-writeable 2>&1", INITCTL_BINARY);12496 cmd = nih_sprintf (NULL, "%s notify-disk-writeable 2>&1", INITCTL_BINARY);
12494 TEST_NE_P (cmd, NULL);12497 TEST_NE_P (cmd, NULL);
12495 RUN_COMMAND (NULL, cmd, &output, &lines);12498 RUN_COMMAND (NULL, cmd, &output, &lines);
1249612499
=== removed file 'util/tests/test_user_sessions.sh'
--- util/tests/test_user_sessions.sh 2012-01-26 08:59:08 +0000
+++ util/tests/test_user_sessions.sh 1970-01-01 00:00:00 +0000
@@ -1,1091 +0,0 @@
1#!/bin/sh
2#---------------------------------------------------------------------
3# Script to run minimal Upstart user session tests.
4#
5# Note that this script _cannot_ be run as part of the "make check"
6# tests since those tests stimulate functions and features of the
7# as-yet-uninstalled version of Upstart. However, this script needs to
8# run on a system where the version of Upstart under test has _already_
9# been fully installed.
10#---------------------------------------------------------------------
11#
12# Copyright (C) 2011 Canonical Ltd.
13#
14# Author: James Hunt <james.hunt@canonical.com>
15#
16# This program is free software: you can redistribute it and/or modify
17# it under the terms of the GNU General Public License as published by
18# the Free Software Foundation, version 3 of the License.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27#
28#---------------------------------------------------------------------
29
30script_name=${0##*/}
31sys_job_dir="/etc/init"
32user_job_dir="$HOME/.init"
33user_log_dir="$HOME/.cache/upstart/log"
34sys_log_dir="/var/log/upstart"
35bug_url="https://bugs.launchpad.net/upstart/+filebug"
36test_dir=
37test_dir_suffix=
38user_to_create=
39uid=
40gid=
41opt=
42OPTARG=
43debug_enabled=0
44feature=
45
46# allow non-priv users to find 'initctl'
47export PATH=$PATH:/sbin
48
49# for assertions
50die()
51{
52 msg="$*"
53 echo "ERROR: $msg" >&2
54 exit 1
55}
56
57debug()
58{
59 str="$1"
60 [ "$debug_enabled" = 1 ] && echo "DEBUG: $str"
61}
62
63get_job_pid()
64{
65 job="$1"
66 [ -z "$job" ] && die "need job"
67
68 pid=$(initctl status "$job"|grep process|awk '{print $NF}')
69 [ -z "$pid" ] && die "job $job has no pid"
70
71 echo "$pid"
72}
73
74# take a string and convert it into a valid job name
75make_job_name()
76{
77 str="$1"
78
79 echo "$str" |\
80 sed -e 's/>/ gt /g' -e 's/</ lt /g' -e 's/+/ and /g' |\
81 sed -e 's/[[:punct:]]//g' -e 's/ */ /g' |\
82 tr ' ' '-'
83}
84
85upstart_encode()
86{
87 str="$1"
88
89 echo "$str" | sed 's!/!_!g'
90}
91
92# take a string and convert it into a valid job log file name
93make_log_name()
94{
95 str="$1"
96 upstart_encode "$str"
97}
98
99TEST_FAILED()
100{
101 args="$*"
102
103 [ -z "$args" ] && die "need args"
104
105 echo
106 echo "ERROR: TEST FAILED ('$feature')"
107 echo
108 printf "BAD: ${args}\n"
109 printf "\nPlease report a bug at $bug_url including the following details:\n"
110 printf "\nUpstart:\n"
111 /sbin/init --version|head -n1
112 /sbin/initctl --version|head -n1
113 echo
114 printf "cmdline:\n"
115 cat /proc/cmdline
116 echo
117 printf "Upstart Env:\n"
118 set|grep UPSTART_
119 echo
120 printf "lsb:\n"
121 lsb_release -a
122 printf "\nuname:\n"
123 uname -a
124 echo
125 sync
126 echo "ERROR: TEST FAILED ('$feature')"
127 echo
128 exit 1
129}
130
131TEST_GROUP()
132{
133 name="$1"
134
135 [ -z "$name" ] && die "need name"
136
137 printf "Testing %s\n" "$name"
138}
139
140TEST_FEATURE()
141{
142 feature="$1"
143
144 [ -z "$feature" ] && die "need feature"
145
146 printf "...%s\n" "$feature"
147}
148
149TEST_NE()
150{
151 cmd="$1"
152 value="$2"
153 expected="$3"
154
155 # XXX: no checks on value or expected since they might be blank
156 [ -z "$cmd" ] && die "need cmd"
157
158 [ "$value" = "$expected" ] && TEST_FAILED \
159 "wrong value for '$cmd', expected $expected got $value"
160}
161
162TEST_EQ()
163{
164 cmd="$1"
165 value="$2"
166 expected="$3"
167
168 # XXX: no checks on value or expected since they might be blank
169 [ -z "$cmd" ] && die "need cmd"
170
171 [ "$value" != "$expected" ] && TEST_FAILED \
172 "wrong value for '$cmd', expected '$expected' got '$value'"
173}
174
175checks()
176{
177 cmd=initctl
178 [ -z "$(command -v $cmd)" ] && die "cannot find command $cmd"
179
180 [ "$(id -u)" = 0 ] && die "ERROR: should not run this function as root"
181
182 # This will fail for a non-root user unless D-Bus is correctly
183 # configured
184 $cmd emit foo || die \
185 "You do not appear to have configured D-Bus for Upstart user sessions. See usage."
186}
187
188setup()
189{
190 uid=$(id -u)
191 gid=$(id -g)
192
193 if [ "$uid" = 0 ]
194 then
195 [ -z "$user_to_create" ] && die "need '-u' option when running as root"
196
197 getent passwd "$user_to_create" && \
198 die "user '$user_to_create' already exists"
199
200 echo "Creating user '$user_to_create'"
201 cmd="useradd -mU -c 'Upstart Test User' $user_to_create"
202 eval "$cmd"
203 TEST_EQ "$cmd" $? 0
204
205 echo "Locking account for user '$user_to_create'"
206 cmd="usermod -L $user_to_create"
207 eval "$cmd"
208 TEST_EQ "$cmd" $? 0
209
210 # Run ourselves again as the new user
211 su -c "$0 -a" "$user_to_create"
212 test_run_rc=$?
213
214 if [ $test_run_rc -eq 0 ]
215 then
216 echo "Deleting user '$user_to_create'"
217 cmd="userdel -r \"$user_to_create\""
218 eval "$cmd"
219 TEST_EQ "$cmd" $? 0
220 fi
221
222 exit $test_run_rc
223 fi
224
225 checks
226
227 # setup
228 if [ ! -d "$user_job_dir" ]
229 then
230 cmd="mkdir -p \"$user_job_dir\""
231 eval $cmd
232 TEST_EQ "$cmd" $? 0
233
234 cmd="chmod 755 \"$user_job_dir\""
235 eval "$cmd"
236 TEST_EQ "$cmd" $? 0
237 fi
238
239 # create somewhere to store user jobs
240 cmd="mktemp -d --tmpdir=\"$user_job_dir\""
241 test_dir=$(eval "$cmd")
242 TEST_EQ "$cmd" $? 0
243 TEST_NE "$test_dir" "$test_dir" ""
244 test_dir_suffix=${test_dir#${user_job_dir}/}
245
246 # ensure files in this directory are accessible since
247 # mktemp sets directory perms to 0700 regardless of umask.
248 cmd="chmod 755 \"$test_dir\""
249 eval "$cmd"
250 TEST_EQ "$cmd" $? 0
251
252 TEST_NE "HOME" "$HOME" ""
253}
254
255cleanup()
256{
257 if [ -d "$test_dir" ]
258 then
259 echo "Removing test directory '$test_dir'"
260 cmd="rmdir \"$test_dir\""
261 eval "$cmd"
262 TEST_EQ "$cmd" $? 0
263 fi
264}
265
266ensure_job_known()
267{
268 job="$1"
269 job_name="$2"
270
271 [ -z "$job" ] && die "no job"
272 [ -z "$job_name" ] && die "no job name"
273
274 TEST_FEATURE "ensure 'initctl' recognises job"
275 initctl list|grep -q "^$job " || \
276 TEST_FAILED "job $job_name not known to initctl"
277
278 TEST_FEATURE "ensure 'status' recognises job"
279 cmd="status ${job}"
280 eval "$cmd" >/dev/null 2>&1
281 rc=$?
282 TEST_EQ "$cmd" $rc 0
283}
284
285# Note that if the specified job is *not* as task, it is expected to run
286# indefinately. This allows us to perform PID checks, etc.
287run_user_job_tests()
288{
289 job_name="$1"
290 job_file="$2"
291 task="$3"
292 env="$4"
293
294 # XXX: env can be empty
295 [ -z "$job_name" ] && die "no job name"
296 [ -z "$job_file" ] && die "no job file"
297 [ -z "$task" ] && die "no task value"
298
299 job="${test_dir_suffix}/${job_name}"
300
301 [ -f "$job_file" ] || TEST_FAILED "job file '$job_file' does not exist"
302
303 ensure_job_known "$job" "$job_name"
304
305 TEST_FEATURE "ensure job can be started"
306 cmd="start ${job} ${env}"
307 output=$(eval "$cmd")
308 rc=$?
309 TEST_EQ "$cmd" $rc 0
310
311 if [ "$task" = no ]
312 then
313 TEST_FEATURE "ensure 'start' shows job pid"
314 pid=$(echo "$output"|awk '{print $4}')
315 TEST_NE "pid" "$pid" ""
316
317 TEST_FEATURE "ensure 'initctl' shows job is running with pid"
318 initctl list|grep -q "^$job start/running, process $pid" || \
319 TEST_FAILED "job $job_name did not start"
320
321 TEST_FEATURE "ensure 'status' shows job is running with pid"
322 cmd="status ${job}"
323 output=$(eval "$cmd")
324 echo "$output"|while read job_tmp state ignored status_pid
325 do
326 state=$(echo $state|tr -d ',')
327 TEST_EQ "job name" "$job_tmp" "$job"
328 TEST_EQ "job state" "$state" "start/running"
329 TEST_EQ "job pid" "$status_pid" "$pid"
330 done
331
332 TEST_FEATURE "ensure job pid is running with correct uids"
333 pid_uids=$(ps --no-headers -p $pid -o euid,ruid)
334 for pid_uid in $pid_uids
335 do
336 TEST_EQ "pid uid" "$pid_uid" "$uid"
337 done
338
339 TEST_FEATURE "ensure job pid is running with correct gids"
340 pid_gids=$(ps --no-headers -p $pid -o egid,rgid)
341 for pid_gid in $pid_gids
342 do
343 TEST_EQ "pid gid" "$pid_gid" "$gid"
344 done
345
346 TEST_FEATURE "ensure process is running in correct directory"
347 cwd=$(readlink /proc/$pid/cwd)
348 TEST_EQ "cwd" "$cwd" "$HOME"
349
350 TEST_FEATURE "ensure job can be stopped"
351 cmd="stop ${job}"
352 output=$(eval "$cmd")
353 rc=$?
354 TEST_EQ "$cmd" $rc 0
355
356 TEST_FEATURE "ensure job pid no longer exists"
357 pid_ids=$(ps --no-headers -p $pid -o euid,ruid,egid,rgid)
358 TEST_EQ "pid uids+gids" "$pid_ids" ""
359 fi
360
361 remove_job_file "$job_file"
362 ensure_job_gone "$job" "$job_name" "$env"
363}
364
365remove_job_file()
366{
367 job_file="$1"
368
369 [ -z "$job_file" ] && die "no job file"
370 [ ! -f "$job_file" ] && TEST_FAILED "job file '$job_file' does not exist"
371
372 cmd="rm $job_file"
373 eval "$cmd"
374 TEST_EQ "$cmd" $? 0
375}
376
377ensure_job_gone()
378{
379 job="$1"
380 job_name="$2"
381 env="$3"
382
383 # XXX: no check on env since it can be empty
384 [ -z "$job" ] && die "no job"
385 [ -z "$job_name" ] && die "no job name"
386
387 TEST_FEATURE "ensure 'initctl' no longer recognises job"
388 initctl list|grep -q "^$job " && \
389 TEST_FAILED "deleted job $job_name still known to initctl"
390
391 TEST_FEATURE "ensure 'status' no longer recognises job"
392 cmd="status ${job}"
393 eval "$cmd" >/dev/null 2>&1
394 rc=$?
395 TEST_NE "$cmd" $rc 0
396}
397
398test_user_job()
399{
400 test_group="$1"
401 job_name="$2"
402 script="$3"
403 task="$4"
404 env="$5"
405
406 # XXX: no test on script or env since they might be empty
407 [ -z "$test_group" ] && die "no test group"
408 [ -z "$job_name" ] && die "no job name"
409 [ -z "$task" ] && die "no task"
410
411 TEST_GROUP "$test_group"
412
413 job_file="${test_dir}/${job_name}.conf"
414
415 echo "$script" > $job_file
416
417 run_user_job_tests "$job_name" "$job_file" "$task" "$env"
418}
419
420test_user_job_binary()
421{
422 group="user job running a binary"
423 job_name="binary_test"
424 script="exec sleep 999"
425 test_user_job "$group" "$job_name" "$script" no ""
426}
427
428test_user_job_binary_task()
429{
430 group="user job running a binary task"
431 job_name="binary_task_test"
432 OUTFILE=$(mktemp)
433
434 script="\
435task
436exec /bin/true > $OUTFILE"
437
438 test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
439 rm -f $OUTFILE
440}
441
442test_user_job_single_line_script()
443{
444 group="user job running a single-line script"
445 job_name="single_line_script_test"
446 script="\
447script
448 sleep 999
449end script"
450 test_user_job "$group" "$job_name" "$script" no ""
451}
452
453test_user_job_single_line_script_task()
454{
455 group="user job running a single-line script task"
456 job_name="single_line_script_task_test"
457 OUTFILE=$(mktemp)
458
459 script="\
460task
461script
462 exec /bin/true > $OUTFILE
463end script"
464 test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
465 rm -f $OUTFILE
466}
467
468test_user_job_multi_line_script()
469{
470 group="user job running a multi-line script"
471 job_name="multi_line_script_test"
472 script="\
473script
474
475 /bin/true
476 /bin/true;/bin/true
477 sleep 999
478
479end script"
480 test_user_job "$group" "$job_name" "$script" no ""
481}
482
483test_user_job_multi_line_script_task()
484{
485 group="user job running a multi-line script task"
486 job_name="multi_line_script_task_test"
487 OUTFILE=$(mktemp)
488
489 script="\
490task
491script
492
493 /bin/true
494 /bin/true
495 /bin/true
496
497end script"
498 test_user_job "$group" "$job_name" "$script" yes "OUTFILE=$OUTFILE"
499 rm -f $OUTFILE
500}
501
502test_user_emit_events()
503{
504 job_name="start_on_foo"
505
506 TEST_GROUP "user emitting an event"
507 initctl emit foo || TEST_FAILED "failed to emit event as user"
508
509 TEST_GROUP "user emitting an event to start a job"
510 script="\
511 start on foo BAR=2
512 stop on baz cow=moo or hello
513 exec sleep 999"
514
515 job_file="${test_dir}/${job_name}.conf"
516 job="${test_dir_suffix}/${job_name}"
517
518 echo "$script" > $job_file
519
520 ensure_job_known "$job" "$job_name"
521
522 initctl list|grep -q "^$job stop/waiting" || \
523 TEST_FAILED "job $job_name not stopped"
524
525 TEST_FEATURE "ensure job can be started with event"
526 initctl emit foo BAR=2 || \
527 TEST_FAILED "failed to emit event for user job"
528
529 initctl status "$job"|grep -q "^$job start/running" || \
530 TEST_FAILED "job $job_name failed to start"
531
532 TEST_FEATURE "ensure job can be stopped with event"
533 initctl emit baz cow=moo || \
534 TEST_FAILED "failed to emit event for user job"
535
536 initctl list|grep -q "^$job stop/waiting" || \
537 TEST_FAILED "job $job_name not stopped"
538
539 rm -f "$job_file"
540}
541
542test_user_job_setuid_setgid()
543{
544 group="user job with setuid and setgid me"
545 job_name="setuid_setgid_me_test"
546 script="\
547setuid $(id -un)
548setgid $(id -gn)
549exec sleep 999"
550 test_user_job "$group" "$job_name" "$script" no ""
551
552 TEST_GROUP "user job with setuid and setgid root"
553 script="\
554setuid root
555setgid root
556exec sleep 999"
557
558 job_name="setuid_setgid_root_test"
559 job_file="${test_dir}/${job_name}.conf"
560 job="${test_dir_suffix}/${job_name}"
561
562 echo "$script" > $job_file
563
564 ensure_job_known "$job" "$job_name"
565
566 TEST_FEATURE "ensure job fails to start as root"
567 cmd="start ${job}"
568 output=$(eval "$cmd" 2>&1)
569 rc=$?
570 TEST_EQ "$cmd" $rc 1
571
572 TEST_FEATURE "ensure 'start' indicates job failure"
573 error=$(echo "$output"|grep failed)
574 TEST_NE "error" "$error" ""
575
576 TEST_FEATURE "ensure 'initctl' does not list job"
577 initctl list|grep -q "^$job stop/waiting" || \
578 TEST_FAILED "job $job_name not listed as stopped"
579
580 delete_job "$job_name"
581}
582
583get_job_file()
584{
585 job_name="$1"
586
587 [ -z "$job_name" ] && die "no job name"
588 echo "${test_dir}/${job_name}.conf"
589}
590
591ensure_no_output()
592{
593 job_name="$1"
594 script="$2"
595 instance="$3"
596
597 job="${test_dir_suffix}/${job_name}"
598
599 create_job "$job_name" "$script"
600 start_job "$job" "$job_name" "$instance"
601
602 check_job_output "$job_name"
603 delete_job "$job_name"
604}
605
606create_job()
607{
608 job_name="$1"
609 script="$2"
610
611 # XXX: script could be empty
612 [ -z "$job_name" ] && die "no job name"
613
614 debug "create_job: job_name='$job_name'"
615 debug "create_job: script='$script'"
616
617 # Not currently possible to have a user job with the
618 # same name as a system job.
619 #
620 # XXX: Note that this test assumes that user has *not* specified
621 # XXX: an alternate configuration directory using the
622 # XXX: '--confdir' option.
623 [ -e "${sys_job_dir}/${job_name}.conf" ] && \
624 die "job '$job_name' already exists as a system job"
625
626 job_file="${test_dir}/${job_name}.conf"
627 job="${test_dir_suffix}/${job_name}"
628
629 echo "$script" > "$job_file"
630 sync
631}
632
633delete_job()
634{
635 job_name="$1"
636
637 [ -z "$job_name" ] && die "no job name"
638
639 job_file="$(get_job_file $job_name)"
640
641 rm "$job_file" || TEST_FAILED "unable to remove job file '$job_file'"
642}
643
644check_job_output()
645{
646 job_name="$1"
647
648 [ ! -z "$(ls $user_log_dir 2>/dev/null)" ] && \
649 TEST_FAILED "job $job_name created logfile unexpectedly in '$user_log_dir'"
650
651 # XXX: note that it might appear that checking in $sys_log_dir
652 # could result in false positives, but this isn't so since
653 # (currently) it is not possible for a user job to have the
654 # same name as a system job. start_job() will detect this
655 # scenario.
656 for dir in "$user_log_dir" "$sys_log_dir"
657 do
658 log_file="${dir}/${job_name}.log"
659 [ -f "$log_file" ] && \
660 TEST_FAILED "job $job_name created logfile unexpectedly as '$log_file'"
661 done
662}
663
664start_job()
665{
666 job="$1"
667 job_file="$2"
668 instance="$3"
669 allow_failure="$4"
670
671 # XXX: instance may be blank
672 [ -z "$job" ] && die "no job"
673 [ -z "$job_file" ] && die "no job file"
674
675 debug "start_job: job='$job'"
676 debug "start_job: job_file='$job_file'"
677 debug "start_job: instance='$instance'"
678 debug "start_job: allow_failure='$allow_failure'"
679
680 eval output=$(mktemp)
681
682 # XXX: Don't quote instance as we don't want to pass a null instance to
683 # start(8).
684 cmd="start \"$job\" $instance >${output} 2>&1"
685 debug "start_job: running '$cmd'"
686 eval "$cmd"
687 rc=$?
688
689 if [ $rc -ne 0 -a -z "$allow_failure" ]
690 then
691 TEST_FAILED "job $job_file not started: $(cat $output)"
692 fi
693
694 rm -f "$output"
695}
696
697get_job_logfile_name()
698{
699 job_name="$1"
700 instance_value="$2"
701
702 # XXX: instance may be null
703 [ -z "$job_name" ] && die "no job name"
704
705 encoded_test_dir_suffix=$(upstart_encode "${test_dir_suffix}/")
706 file_name="${encoded_test_dir_suffix}$(make_log_name $job_name)"
707
708 if [ ! -z "$instance_value" ]
709 then
710 log_file="${user_log_dir}/${file_name}-${instance_value}.log"
711 else
712 log_file="${user_log_dir}/${file_name}.log"
713 fi
714
715 echo "$log_file"
716}
717
718run_job()
719{
720 job="$1"
721 job_name="$2"
722 script="$3"
723 instance="$4"
724
725 # XXX: script, instance might be blank
726 [ -z "$job" ] && die "no job"
727 [ -z "$job_name" ] && die "no job name"
728
729 debug "run_job: job='$job'"
730 debug "run_job: job_name='$job_name'"
731 debug "run_job: script='$script'"
732 debug "run_job: instance='$instance'"
733
734 create_job "$job_name" "$script"
735 start_job "$job" "$job_name" "$instance"
736}
737
738ensure_file_meta()
739{
740 file="$1"
741 expected_owner="$2"
742 expected_group="$3"
743 expected_perms="$4"
744
745 [ -z "$file" ] && die "no file"
746 [ -z "$expected_owner" ] && die "no expected owner"
747 [ -z "$expected_group" ] && die "no expected group"
748 [ -z "$expected_perms" ] && die "no expected perms"
749
750 [ ! -f "$file" ] && die "file $file does not exist"
751
752 expected_perms="640"
753 umask_value=$(umask)
754 umask_expected=0022
755
756 if [ "$umask_value" != "$umask_expected" ]
757 then
758 msg="umask value is $umask_value -"
759 msg="${msg} changing it to $umask_expected."
760 echo "WARNING: $msg"
761 umask "$umask_expected" || TEST_FAILED "unable to change umask"
762 fi
763
764 owner=$(ls -l "$file"|awk '{print $3}')
765 group=$(ls -l "$file"|awk '{print $4}')
766 perms=$(stat --printf "%a\n" "$file")
767
768 [ "$owner" = "$expected_owner" ] || TEST_FAILED \
769 "file $file has wrong owner (expected $expected_owner, got $owner)"
770
771 [ "$group" = "$expected_group" ] || TEST_FAILED \
772 "file $file has wrong group (expected $expected_group, got $group)"
773
774 [ "$perms" = "$expected_perms" ] || TEST_FAILED \
775 "file $file has wrong group (expected $expected_perms, got $perms)"
776}
777
778
779ensure_output()
780{
781 job_name="$1"
782 script="$2"
783 expected_output="$3"
784 instance="$4"
785 instance_value="$5"
786 options="$6"
787
788 # XXX: remaining args could be null
789 [ -z "$job_name" ] && die "no job name"
790
791 debug "ensure_output: job_name='$job_name'"
792 debug "ensure_output: script='$script'"
793 debug "ensure_output: expected_ouput='$expected_ouput'"
794 debug "ensure_output: instance='$instance'"
795 debug "ensure_output: instance_value='$instance_value'"
796 debug "ensure_output: options='$options'"
797
798 regex=n
799 retain=n
800 unique=""
801 use_od=n
802
803 for opt in $options
804 do
805 case "$opt" in
806 regex)
807 regex=y
808 ;;
809 retain)
810 retain=y
811 ;;
812 unique)
813 unique='|sort -u'
814 ;;
815 use_od)
816 use_od=y
817 ;;
818 esac
819 done
820
821 debug "ensure_output: regex='$regex'"
822 debug "ensure_output: retain='$retain'"
823 debug "ensure_output: unique='$unique'"
824 debug "ensure_output: use_od='$use_od'"
825
826 expected_owner=$(id -un)
827 expected_group=$(id -gn)
828 expected_perms="640"
829
830 job="${test_dir_suffix}/${job_name}"
831
832 run_job "$job" "$job_name" "$script" "$instance"
833
834 debug "ensure_output: user_log_dir='$user_log_dir'"
835 debug "ensure_output: test_dir='$test_dir'"
836 debug "ensure_output: test_dir_suffix='$test_dir_suffix'"
837
838 log_file=$(get_job_logfile_name "$job_name" "$instance_value")
839
840 debug "ensure_output: log_file='$log_file'"
841
842 # Give Upstart a chance to parse the file
843 count=1
844 while ! status "$job" >/dev/null 2>&1
845 do
846 sleep 1
847 count=$((count+1))
848 [ "$count" -eq 5 ] && break
849 done
850
851 # give job a chance to start
852 count=1
853 while [ ! -f "$log_file" ]
854 do
855 sleep 1
856 count=$((count+1))
857 [ "$count" -eq 5 ] && break
858 done
859
860 [ ! -f "$log_file" ] && \
861 TEST_FAILED "job '$job_name' failed to create logfile"
862
863 ensure_file_meta \
864 "$log_file" \
865 "$expected_owner" \
866 "$expected_group" \
867 "$expected_perms"
868
869 # XXX: note we have to remove carriage returns added by the line
870 # discipline
871 if [ "$regex" = y ]
872 then
873 log=$(eval "cat $log_file|tr -d '\r' $unique")
874 msg="job '$job_name' failed to log correct data\n"
875 msg="${msg}\texpected regex: '$expected_output'\n"
876 msg="${msg}\tgot : '$log'"
877 cat "$log_file" | egrep "$expected_output" || TEST_FAILED "$msg"
878 elif [ "$use_od" = y ]
879 then
880 log=$(eval "cat $log_file|tr -d '\r' $unique|od -x")
881 msg="job '$job_name' failed to log correct data\n"
882 msg="${msg}\texpected hex: '$expected_output'\n"
883 msg="${msg}\tgot : '$log'"
884 [ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
885 else
886 log=$(eval "cat $log_file|tr -d '\r' $unique")
887 msg="job '$job_name' failed to log correct data\n"
888 msg="${msg}\texpected text: '$expected_output'\n"
889 msg="${msg}\tgot : '$log'"
890 [ "$expected_output" != "$log" ] && TEST_FAILED "$msg"
891 fi
892
893 if [ "$retain" = n ]
894 then
895 delete_job "$job_name"
896 rm "$log_file" || TEST_FAILED "unable to remove log file '$log_file'"
897 fi
898}
899
900test_ensure_no_unexpected_output()
901{
902 #---------------------------------------------------------------------
903 feature="ensure command job does not create log file with no console"
904 TEST_FEATURE "$feature"
905
906 job_name=$(make_job_name "$feature")
907
908 script="\
909 console none
910 exec echo hello world"
911
912 ensure_no_output "$job_name" "$script" ""
913
914 #---------------------------------------------------------------------
915 feature="ensure 1-line script job does not create log file with no console"
916 TEST_FEATURE "$feature"
917
918 job_name=$(make_job_name "$feature")
919
920 script="\
921 console none
922 script
923 echo hello world
924 end script
925 "
926
927 ensure_no_output "$job_name" "$script" ""
928
929 #---------------------------------------------------------------------
930 feature="ensure multi-line script job does not create log file with no console"
931 TEST_FEATURE "$feature"
932
933 job_name=$(make_job_name "$feature")
934
935 script="\
936 console none
937 script
938 /bin/true
939 echo hello world
940 end script
941 "
942
943 ensure_no_output "$job_name" "$script" ""
944
945 #---------------------------------------------------------------------
946 feature="ensure no output if log directory does not exist"
947 TEST_FEATURE "$feature"
948
949 rmdir "${user_log_dir}" || \
950 TEST_FAILED "unable to delete log directory '$user_log_dir'"
951
952 job_name=$(make_job_name "$feature")
953 string="hello world"
954 script="\
955 console log
956 script
957 /bin/true
958 /bin/echo hello world
959 end script
960 "
961
962 ensure_no_output "$job_name" "$script" ""
963
964 mkdir "${user_log_dir}" || \
965 TEST_FAILED "unable to recreate log directory '$user_log_dir'"
966
967 #---------------------------------------------------------------------
968 feature="ensure command job does not create log file with invalid command"
969 TEST_FEATURE "$feature"
970
971 job_name=$(make_job_name "$feature")
972
973 script="\
974 console log
975 exec /this/command/does/not/exist"
976
977 job="${test_dir_suffix}/${job_name}"
978 create_job "$job_name" "$script"
979 start_job "$job" "$job_name" "" 1
980 check_job_output "$job_name"
981 delete_job "$job_name"
982}
983
984test_output_logged()
985{
986 # XXX: upstart won't create this
987 mkdir -p "$user_log_dir"
988
989 test_ensure_no_unexpected_output
990}
991
992test_user_jobs()
993{
994 test_user_job_binary
995 test_user_job_single_line_script
996 test_user_job_multi_line_script
997
998 test_user_job_binary_task
999 test_user_job_single_line_script_task
1000 test_user_job_multi_line_script_task
1001
1002 test_user_job_setuid_setgid
1003
1004 test_user_emit_events
1005
1006 test_output_logged
1007}
1008
1009tests()
1010{
1011 echo
1012 echo -n "Running Upstart user session tests as user '`whoami`'"
1013 echo " (uid $uid, gid $gid) in directory '$test_dir'"
1014 echo
1015
1016 test_user_jobs
1017
1018 echo
1019 echo "All tests completed successfully"
1020 echo
1021}
1022
1023usage()
1024{
1025cat <<EOT
1026USAGE: $script_name [options]
1027
1028OPTIONS:
1029
1030 -a : Actually run this script.
1031 -h : Show this help.
1032 -u <user> : Specify name of test user to create.
1033
1034DESCRIPTION:
1035
1036Run simple set of Upstart user session tests.
1037
1038PREREQUISITE:
1039
1040For this test to run, non-root users must be allowed to invoke all D-Bus
1041methods on Upstart via configuration file:
1042
1043 /etc/dbus-1/system.d/Upstart.conf
1044
1045See dbus-daemon(1) for further details.
1046
1047WARNING: Note that this script is unavoidably invasive, so read what
1048WARNING: follows before running!
1049
1050If run as a non-root user, this script will create a uniquely-named
1051subdirectory below "\$HOME/.init/" to run its tests in. On successful
1052completion of these tests, the unique subdirectory and its contents will
1053be removed.
1054
1055If however, this script is invoked as the root user, the script will
1056refuse to run until given the name of a test user to create via the "-u"
1057option. If the user specified to this option already exists, this script
1058will exit with an error. If the user does not already exist, it will be
1059created, the script then run *as that user* and assuming successful
1060completion of the tests, the test user and their home directory will
1061then be deleted.
1062
1063EOT
1064}
1065
1066#---------------------------------------------------------------------
1067# main
1068#---------------------------------------------------------------------
1069
1070while getopts "dhu:" opt
1071do
1072 case "$opt" in
1073 d)
1074 debug_enabled=1
1075 ;;
1076
1077 h)
1078 usage
1079 exit 0
1080 ;;
1081
1082 u)
1083 user_to_create="$OPTARG"
1084 ;;
1085 esac
1086done
1087
1088setup
1089tests
1090cleanup
1091exit 0

Subscribers

People subscribed via source and target branches