Bug 1019850

Summary: passwd -S <username> output is one day off
Product: Red Hat Enterprise Linux 6 Reporter: John Trowbridge <jtrowbri>
Component: passwdAssignee: Miloslav Trmač <mitr>
Status: CLOSED WONTFIX QA Contact: BaseOS QE Security Team <qe-baseos-security>
Severity: low Docs Contact:
Priority: low    
Version: 6.4CC: jtrowbri
Target Milestone: rc   
Target Release: ---   
Hardware: All   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of:
: 1019855 (view as bug list) Environment:
Last Closed: 2013-11-21 19:08:18 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Bug Depends On:    
Bug Blocks: 1019855    

Description John Trowbridge 2013-10-16 13:40:55 UTC
Description of problem:
When changing a password and then running 'passwd -S user_id' the date
shows up as yesterday. 


Version-Release number of selected component (if applicable):
passwd 0.77-4.el6_2.2.x86_64

How reproducible:
Easily reproducible

Steps to Reproduce:
1. passwd testuser
2. passwd -S testuser


Actual results:
testuser PS 2013-10-15 0 99999 7 -1 (Password set, MD5 crypt.)

Expected results:
testuser PS 2013-10-16 0 99999 7 -1 (Password set, MD5 crypt.)

Additional info:
passwd/passwd-0.77/libuser.c

287                         sp_lstchg = (time_t) ent_value_int64(ent, LU_SHADOWLASTCHANGE);
..

324                         if (shadow) {
325                                 sp_lstchg = sp_lstchg * 24L * 3600L;
326                                 localtime_r(&sp_lstchg, &tm);
327                                 strftime(date, sizeof(date), "%Y-%m-%d", &tm);
328                                 printf("%s %s %s %lld %lld %lld %lld (%s)\n", realname, status,
329                                         date, sp_min, sp_max, sp_warn, sp_inact, msg);
..

There are a few conversions that happen in order to get to the final value, which includes the last changed value pulled, and run through localtime_r. Something in the conversion is probably off a bit (or doesnt compensate for tz info, DST, whatever)

Comment 1 Miloslav Trmač 2013-10-21 20:35:56 UTC
Thanks for your report.

Could you provide _complete_ steps to reproduce, please?  These would include at least:
* The current date when changing the password, as output by date(1)
* Your timezone setting
* The output of (passwd -S)
The relevant line from the /etc/shadow would also be helpful.


What I suspect is happening, is that the sp_lstchg field is only stored only with a precision of days, but it is measured as days since Jan 1, 1970 _UTC_.  So, the time when a day starts/ends for sp_lstchg may differ from the time the day starts/ends in your time zone.

E.g. at time_t value 1381874401, in time zone Europe/Prague:
$ LC_ALL=C date -d @1381874401
Wed Oct 16 00:00:01 CEST 2013
$ LC_ALL=C date -u -d @1381874401
Tue Oct 15 22:00:01 UTC 2013

The sp_lstchg value used for @1381874401 is 15993.  This value applies for all times between @1381795200 and @1381881599, i.e. between Tue Oct 15 02:00:00 CEST 2013 and Wed Oct 16 01:59:59 CEST 2013.

Due to small precision of the sp_lstchg value, it's impossible to reconstruct the precise time, so the tools output the first date in the interval (Oct 15).

One possible way to produce a "precise" output is to have (passwd -S) output a date in the UTC time zone; then there would be only one date to output - OTOH it would be a different date than the user probably has in mind, and changing the semantics of the output from local time to UTC would be an incompatible change, possibly breaking scripts.


Anyway, all of the above is a guess.  Precise steps to reproduce would help diagnosing the actual cause.

Comment 2 John Trowbridge 2013-10-21 20:48:57 UTC
Here are the requested reproduction steps:

[root@rhel ~]# date
Mon Oct 21 16:40:06 EDT 2013
[root@rhel ~]# useradd testuser
[root@rhel ~]# passwd testuser
Changing password for user testuser.
New password: 
BAD PASSWORD: it is too short
BAD PASSWORD: is too simple
Retype new password: 
passwd: all authentication tokens updated successfully.
[root@rhel ~]# passwd -S testuser
testuser PS 2013-10-20 0 99999 7 -1 (Password set, SHA512 crypt.)
You have new mail in /var/spool/mail/root
[root@rhel ~]# grep testuser /etc/shadow
testuser:$6$aCg.SHv5$IBaMt73GZoZnDW22M77MYL9LHWqM3Gev9uOUPS.nO/QfG6WgCMR3KMJh88Dbw8/pJoUNuP0YGaivJ9MqaZ/Ru0:15999:0:99999:7:::

The following python code shows that the number of days in /etc/shadow gives the expected date:

>>> import datetime
>>> epoch = datetime.date(1970,1,1)
>>> shadow = epoch + datetime.timedelta(15999)
>>> shadow
2013-10-21

Comment 4 Miloslav Trmač 2013-11-21 19:08:18 UTC
Thanks.

(In reply to John Trowbridge from comment #2)
> Here are the requested reproduction steps:
> 
> [root@rhel ~]# date
> Mon Oct 21 16:40:06 EDT 2013

This is @1382388006, or Mon Oct 21 20:40:06 UTC 2013.

> [root@rhel ~]# grep testuser /etc/shadow
> testuser:$6$aCg.SHv5$IBaMt73GZoZnDW22M77MYL9LHWqM3Gev9uOUPS.nO/
> QfG6WgCMR3KMJh88Dbw8/pJoUNuP0YGaivJ9MqaZ/Ru0:15999:0:99999:7:::
15999 is correct, and corresponds to @1382313600, i.e. Mon Oct 21 00:00:00 UTC 2013, which is Sun Oct 20 20:00:00 EDT 2013.

> [root@rhel ~]# passwd -S testuser
> testuser PS 2013-10-20 0 99999 7 -1 (Password set, SHA512 crypt.)

Hence the 2013-10-20 output.

> The following python code shows that the number of days in /etc/shadow gives
> the expected date:
> 
> >>> import datetime
> >>> epoch = datetime.date(1970,1,1)
> >>> shadow = epoch + datetime.timedelta(15999)
> >>> shadow
> 2013-10-21
No, this uses the epoch in your local time zone; the actual epoch is in UTC.


Overall, there are only two reasonable settings: report the date in local time (which results in the problem you have encountered) and report the date in UTC.

Reporting it in UTC isn't a general fix: Take e.g. @1384995600:
> $ LC_ALL=C date -d @1384995600 -u
> Thu Nov 21 01:00:00 UTC 2013
> $ LC_ALL=C TZ=/usr/share/zoneinfo/America/New_York date -d @1384995600
> Wed Nov 20 20:00:00 EST 2013
This rounds down to sp_lstchg value 16030, i.e.
> $ LC_ALL=C date -d @1384992000 -u
> Thu Nov 21 00:00:00 UTC 2013
> $ TZ=/usr/share/zoneinfo/America/New_York LC_ALL=C date -d @1384992000
> Wed Nov 20 19:00:00 EST 2013

and the date reported in UTC is Nov 21, a mismatch with the local time which was on Nov 20.


As an imprecise illustration of the tradeofs, assuming timezones are regularly distributed in the (-12,+12) offset range, reporting the password change date in local time zone results in the date being "correct" when compared to a local-timezone date for 50% of cases, and reporting the password change date in UTC results in it being correct in 75% cases.  So we are talking about an incompatible change in output to increase the chance that the output is correct but still not making the output at all reliable.  (As more US-specific examples, for the UTC+6 time zone, the difference is between being correct in 25% and 75% of times of day; for the UTC+8 time zone, betseen 33% and 66% of times of day.)  I don't think the risk of breaking existing scripts is worth it in this case.


Ultimately the only real fix is to start storing the last change time in more precision than "24 hours" (filed now as bug 1033252), which I'm afraid can't happen in RHEL 6.

For RHEL 6, we can make the output more likely to be correct but still not reliable, at the cost of breaking compatibility (... in an undocumented output format, true); I don't think this is worth it, so closing wontfix (but please do reopen if you disagree.)