43

Cron doesn't use the path of the user whose crontab it is and, instead, has its own. It can easily be changed by adding PATH=/foo/bar at the beginning of the crontab, and the classic workaround is to always use absolute paths to commands run by cron, but where is cron's default PATH defined?

I created a crontab with the following contents on my Arch system (cronie 1.5.1-1) and also tested on an Ubuntu 16.04.3 LTS box with the same results:

$ crontab -l
* * * * * echo "$PATH" > /home/terdon/fff

That printed:

$ cat fff
/usr/bin:/bin

But why? The default system-wide path is set in /etc/profile, but that includes other directories:

$ grep PATH= /etc/profile
PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"

There is nothing else relevant in /etc/environment or /etc/profile.d, the other files I thought might possibly be read by cron:

$ grep PATH= /etc/profile.d/* /etc/environment
/etc/profile.d/jre.sh:export PATH=${PATH}:/usr/lib/jvm/default/bin
/etc/profile.d/mozilla-common.sh:export MOZ_PLUGIN_PATH="/usr/lib/mozilla/plugins"
/etc/profile.d/perlbin.sh:[ -d /usr/bin/site_perl ] && PATH=$PATH:/usr/bin/site_perl
/etc/profile.d/perlbin.sh:[ -d /usr/lib/perl5/site_perl/bin ] && PATH=$PATH:/usr/lib/perl5/site_perl/bin
/etc/profile.d/perlbin.sh:[ -d /usr/bin/vendor_perl ] && PATH=$PATH:/usr/bin/vendor_perl
/etc/profile.d/perlbin.sh:[ -d /usr/lib/perl5/vendor_perl/bin ] && PATH=$PATH:/usr/lib/perl5/vendor_perl/bin
/etc/profile.d/perlbin.sh:[ -d /usr/bin/core_perl ] && PATH=$PATH:/usr/bin/core_perl

There is also nothing relevant in any of the files in /etc/skel, unsurprisingly, nor is it being set in any /etc/cron* file:

$ grep PATH /etc/cron* /etc/cron*/*
grep: /etc/cron.d: Is a directory
grep: /etc/cron.daily: Is a directory
grep: /etc/cron.hourly: Is a directory
grep: /etc/cron.monthly: Is a directory
grep: /etc/cron.weekly: Is a directory
/etc/cron.d/0hourly:PATH=/sbin:/bin:/usr/sbin:/usr/bin

So, where is cron's default PATH for user crontabs being set? Is it hardcoded in cron itself? Doesn't it read some sort of configuration file for this?

terdon
  • 242,166
  • 3
    There is no reason for cron to look at /etc/profile, or care about any particular shell. A better question is why doesn't cron read PATH from login.defs (on Linux) or login.conf (on *BSD). I suppose it's ultimately an implementation detail. – Satō Katsura Oct 31 '17 at 13:02
  • @SatōKatsura sure, I only mentioned /etc/profile because it uses the same syntax (var=value) as cron itself, so it would be easy enough to do and /etc/profile is, to my knowledge, very widespread. What surprised me is that I couldn't find it being set anywhere so it looked like it was hard coded. As is indeed the case, as Stephen explained below. – terdon Oct 31 '17 at 13:05
  • People using zsh as their interactive shell don't care about /etc/profile (which is specific to bash) – Basile Starynkevitch Oct 31 '17 at 13:16
  • 2
    @BasileStarynkevitch no, it is not specific to bash at all! Quite the contrary! While there are some shells that don't read it (the c-shell family AFAIK), zsh is not one of them. See the zsh manpage, if you don't believe me. In any case, interactive shells are irrelevant since the various profile files are only read by login shells, anyway. These may, or may not be interactive. – terdon Oct 31 '17 at 13:20
  • 1
    Sometimes running strings against a program can help find these hard-coded values, too. – jrw32982 Nov 02 '17 at 02:34
  • @Christopher - whether or not crontab reads a configuration file is irrelevant to what configuration is used by cron itself: crontab is just a program that is used to update the database of job tables used by cron and signal cron when it has changed. cron (if the version in use is anywhere near standard) should be reading /etc/crontab at startup. – Jules Nov 08 '17 at 00:33
  • @terdon - in simple cases, /etc/profile uses the same syntax as cron for setting the path, but note that it is written in a general purpose programming language that is able to manipulate references to variables and to evaluate generated code, so in the general case extracting the path information from it is much more complex than what cron does with its own files. – Jules Nov 08 '17 at 00:38
  • @Jules What do you mean? /etc/profile should be written in POSIX sh and is sourced (.) by POSIX compliant (or semi-compliant, like bash, zsh etc.) shells. Given that cron also uses sh to run its commands, it would be trivial to source it each time it launches its sh. – terdon Nov 08 '17 at 08:29
  • @terdon - cron can be configured to use any shell. It can't rely on the user-specified shell being able to understand /etc/profile. – Jules Nov 08 '17 at 10:06
  • @Jules it can? How? Calling a command with an explicit shell is not configuring cron, neither is setting a variable in a crontab. sh is not the "user-specified" shell, it is the default shell on *nix systems and is usually either a simple POSIX-compliant shell like dash, or actual bourne-sh or bash running in POSIX mode. either way, cron defaults to /bin/sh so it could just as well have defaulted to reading /etc/profile or, as Sato correctly pointed out, login.[defs|conf]. – terdon Nov 08 '17 at 10:17

2 Answers2

57

It’s hard-coded in the source code (that link points to the current Debian cron — given the variety of cron implementations, it’s hard to choose one, but other implementations are likely similar):

#ifndef _PATH_DEFPATH
# define _PATH_DEFPATH "/usr/bin:/bin"
#endif

cron doesn’t read default paths from a configuration file; I imagine the reasoning there is that it supports specifying paths already using PATH= in any cronjob, so there’s no need to be able to specify a default elsewhere. (The hard-coded default is used if nothing else specified a path in a job entry.)

Stephen Kitt
  • 434,908
  • Note that, despite the existence of the _PATH_DEFPATH_ROOT define, I confirmed (using a cron job of echo $PATH > /testfile) after editing root's crontab using crontab -e on Debian Stretch that root's crontab also uses _PATH_DEFPATH, i.e. "/usr/bin:/bin", not _PATH_DEFPATH_ROOT. This is also confirmed by the second source code link in this answer (in which _PATH_DEFPATH_ROOT is not used). It's not clear to me whether this orphaned define is a bug. – njahnke May 14 '18 at 19:18
13

Adding to Stephen Kitt's answer, there is a configuration file that sets PATH for cron on Ubuntu, and cron ignores that PATH to use the hard-coded default (or PATHs set in the crontabs themselves). The file is /etc/environment. Note cron's PAM configuration:

$ cat /etc/pam.d/cron
...   
# Read environment variables from pam_env's default files, /etc/environment
# and /etc/security/pam_env.conf.
session       required   pam_env.so

# In addition, read system locale information
session       required   pam_env.so envfile=/etc/default/locale
...

This is easily verifiable. Add a variable to /etc/environment, say foo=bar, run env > /tmp/foo as a cronjob and watch as foo=bar shows up in the output.


But why? The default system-wide path is set in /etc/profile, but that includes other directories:

$ grep PATH= /etc/profile
PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"

That's true in Arch Linux, but in Ubuntu, the base PATH is set in /etc/environment. Files in /etc/profile.d tack on to an existing PATH, and you can append to it in ~/.pam_environment. I have a bug filed about Arch's behaviour.

Unfortunately, /etc/pam.d/cron does not include reading from ~/.pam_environment. Weirdly, /etc/pam.d/atd does include that file:

$ cat /etc/pam.d/atd
#
# The PAM configuration file for the at daemon
#

@include common-auth
@include common-account
session    required   pam_loginuid.so
@include common-session-noninteractive
session    required   pam_limits.so
session    required   pam_env.so user_readenv=1

... but commands run via at apparently inherit the environment available when creating the at job (for example, env -i /usr/bin/at ... seems to run jobs with a very clean environment).

Amending /etc/pam.d/cron to have user_readenv=1 seems to cause no problems, and variables in ~/.pam_environment started showing up fine (except for PATH, of course).


All told, setting environment variables for cron seems to be a messy business. The best place seems to be in the job specification itself, if only because you don't know which inherited environment variables cron might decide ignore (without reading the source).

muru
  • 72,889
  • Regarding at jobs, if you dump an at job you’ll see it explicitly sets the environment up to match the environment when the job was created. – Stephen Kitt Nov 01 '17 at 18:42