summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <julian.klode@canonical.com>2018-01-29 16:15:41 +0100
committerJulian Andres Klode <julian.klode@canonical.com>2018-02-19 16:05:01 +0100
commit9e5899cac1a6367e3769af52a724821880e538f6 (patch)
tree4c7add81aeb5f851ce764697b03e3778391f9cbc
parent38d444af2632219ab399dabadaaefaa4dcdd6ebf (diff)
Check that Date of Release file is not in the future
By restricting the Date field to be in the past, an attacker cannot just create a repository from the future that would be accepted as a valid update for a repository. This check can be disabled by Acquire::Check-Date set to false. This will also disable Check-Valid-Until and any future date related checking, if any - the option means: "my computers date cannot be trusted." Modify the tests to allow repositories to be up to 10 hours in the future, so we can keep using hours there to simulate time changes.
-rw-r--r--apt-pkg/acquire-item.cc19
-rw-r--r--apt-pkg/deb/debmetaindex.cc136
-rw-r--r--apt-pkg/deb/debmetaindex.h4
-rw-r--r--apt-pkg/metaindex.cc7
-rw-r--r--apt-pkg/metaindex.h1
-rw-r--r--apt-pkg/sourcelist.cc2
-rw-r--r--debian/NEWS16
-rw-r--r--doc/apt.conf.5.xml21
-rw-r--r--doc/examples/configure-index3
-rw-r--r--doc/sources.list.5.xml17
-rw-r--r--test/integration/framework2
-rwxr-xr-xtest/integration/test-releasefile-date46
12 files changed, 231 insertions, 43 deletions
diff --git a/apt-pkg/acquire-item.cc b/apt-pkg/acquire-item.cc
index 792465b90..ff4036f4a 100644
--- a/apt-pkg/acquire-item.cc
+++ b/apt-pkg/acquire-item.cc
@@ -1661,6 +1661,25 @@ bool pkgAcqMetaBase::VerifyVendor(string const &) /*{{{*/
}
}
+ if (TransactionManager->MetaIndexParser->GetNotBefore() > 0)
+ {
+ time_t const invalid_for = TransactionManager->MetaIndexParser->GetNotBefore() - time(nullptr);
+ if (invalid_for > 0)
+ {
+ std::string errmsg;
+ strprintf(errmsg,
+ // TRANSLATOR: The first %s is the URL of the bad Release file, the second is
+ // the time until the file will be valid - formatted in the same way as in
+ // the download progress display (e.g. 7d 3h 42min 1s)
+ _("Release file for %s is not valid yet (invalid for another %s). "
+ "Updates for this repository will not be applied."),
+ Target.URI.c_str(), TimeToStr(invalid_for).c_str());
+ if (ErrorText.empty())
+ ErrorText = errmsg;
+ return _error->Error("%s", errmsg.c_str());
+ }
+ }
+
/* Did we get a file older than what we have? This is a last minute IMS hit and doubles
as a prevention of downgrading us to older (still valid) files */
if (TransactionManager->IMSHit == false && TransactionManager->LastMetaIndexParser != NULL &&
diff --git a/apt-pkg/deb/debmetaindex.cc b/apt-pkg/deb/debmetaindex.cc
index 59a26390e..6cbed85a7 100644
--- a/apt-pkg/deb/debmetaindex.cc
+++ b/apt-pkg/deb/debmetaindex.cc
@@ -49,12 +49,16 @@ class APT_HIDDEN debReleaseIndexPrivate /*{{{*/
time_t ValidUntilMin;
time_t ValidUntilMax;
+ metaIndex::TriState CheckDate;
+ time_t DateMaxFuture;
+ time_t NotBefore;
+
std::vector<std::string> Architectures;
std::vector<std::string> NoSupportForAll;
std::vector<std::string> SupportedComponents;
std::map<std::string, std::string> const ReleaseOptions;
- debReleaseIndexPrivate(std::map<std::string, std::string> const &Options) : CheckValidUntil(metaIndex::TRI_UNSET), ValidUntilMin(0), ValidUntilMax(0), ReleaseOptions(Options) {}
+ debReleaseIndexPrivate(std::map<std::string, std::string> const &Options) : CheckValidUntil(metaIndex::TRI_UNSET), ValidUntilMin(0), ValidUntilMax(0), CheckDate(metaIndex::TRI_UNSET), DateMaxFuture(0), NotBefore(0), ReleaseOptions(Options) {}
};
/*}}}*/
// ReleaseIndex::MetaIndex* - display helpers /*{{{*/
@@ -482,54 +486,77 @@ bool debReleaseIndex::Load(std::string const &Filename, std::string * const Erro
Date = 0;
}
- bool CheckValidUntil = _config->FindB("Acquire::Check-Valid-Until", true);
- if (d->CheckValidUntil == metaIndex::TRI_NO)
- CheckValidUntil = false;
- else if (d->CheckValidUntil == metaIndex::TRI_YES)
- CheckValidUntil = true;
+ bool CheckDate = _config->FindB("Acquire::Check-Date", true);
+ if (d->CheckDate == metaIndex::TRI_NO)
+ CheckDate = false;
+ else if (d->CheckDate == metaIndex::TRI_YES)
+ CheckDate = true;
- if (CheckValidUntil == true)
+ if (CheckDate)
{
- std::string const StrValidUntil = Section.FindS("Valid-Until");
-
- // if we have a Valid-Until header in the Release file, use it as default
- if (StrValidUntil.empty() == false)
- {
- if(RFC1123StrToTime(StrValidUntil.c_str(), ValidUntil) == false)
- {
- if (ErrorText != NULL)
- strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Valid-Until", Filename.c_str());
- return false;
- }
- }
auto const Label = GetLabel();
- // get the user settings for this archive and use what expires earlier
- time_t MaxAge = d->ValidUntilMax;
- if (MaxAge == 0)
- {
- MaxAge = _config->FindI("Acquire::Max-ValidTime", 0);
- if (Label.empty() == false)
- MaxAge = _config->FindI(("Acquire::Max-ValidTime::" + Label).c_str(), MaxAge);
- }
- time_t MinAge = d->ValidUntilMin;
- if (MinAge == 0)
+ // get the user settings for this archive
+ time_t MaxFuture = d->DateMaxFuture;
+ if (MaxFuture == 0)
{
- MinAge = _config->FindI("Acquire::Min-ValidTime", 0);
+ MaxFuture = _config->FindI("Acquire::Max-FutureTime", 10);
if (Label.empty() == false)
- MinAge = _config->FindI(("Acquire::Min-ValidTime::" + Label).c_str(), MinAge);
+ MaxFuture = _config->FindI(("Acquire::Max-FutureTime::" + Label).c_str(), MaxFuture);
}
- if (MinAge != 0 || ValidUntil != 0 || MaxAge != 0)
+ d->NotBefore = Date - MaxFuture;
+
+ bool CheckValidUntil = _config->FindB("Acquire::Check-Valid-Until", true);
+ if (d->CheckValidUntil == metaIndex::TRI_NO)
+ CheckValidUntil = false;
+ else if (d->CheckValidUntil == metaIndex::TRI_YES)
+ CheckValidUntil = true;
+
+ if (CheckValidUntil == true)
{
- if (MinAge != 0 && ValidUntil != 0) {
- time_t const min_date = Date + MinAge;
- if (ValidUntil < min_date)
- ValidUntil = min_date;
+ std::string const StrValidUntil = Section.FindS("Valid-Until");
+
+ // if we have a Valid-Until header in the Release file, use it as default
+ if (StrValidUntil.empty() == false)
+ {
+ if (RFC1123StrToTime(StrValidUntil.c_str(), ValidUntil) == false)
+ {
+ if (ErrorText != NULL)
+ strprintf(*ErrorText, _("Invalid '%s' entry in Release file %s"), "Valid-Until", Filename.c_str());
+ return false;
+ }
+ }
+ auto const Label = GetLabel();
+ // get the user settings for this archive and use what expires earlier
+ time_t MaxAge = d->ValidUntilMax;
+ if (MaxAge == 0)
+ {
+ MaxAge = _config->FindI("Acquire::Max-ValidTime", 0);
+ if (Label.empty() == false)
+ MaxAge = _config->FindI(("Acquire::Max-ValidTime::" + Label).c_str(), MaxAge);
+ }
+ time_t MinAge = d->ValidUntilMin;
+ if (MinAge == 0)
+ {
+ MinAge = _config->FindI("Acquire::Min-ValidTime", 0);
+ if (Label.empty() == false)
+ MinAge = _config->FindI(("Acquire::Min-ValidTime::" + Label).c_str(), MinAge);
}
- if (MaxAge != 0 && Date != 0) {
- time_t const max_date = Date + MaxAge;
- if (ValidUntil == 0 || ValidUntil > max_date)
- ValidUntil = max_date;
+
+ if (MinAge != 0 || ValidUntil != 0 || MaxAge != 0)
+ {
+ if (MinAge != 0 && ValidUntil != 0)
+ {
+ time_t const min_date = Date + MinAge;
+ if (ValidUntil < min_date)
+ ValidUntil = min_date;
+ }
+ if (MaxAge != 0 && Date != 0)
+ {
+ time_t const max_date = Date + MaxAge;
+ if (ValidUntil == 0 || ValidUntil > max_date)
+ ValidUntil = max_date;
+ }
}
}
}
@@ -566,6 +593,11 @@ bool debReleaseIndex::Load(std::string const &Filename, std::string * const Erro
return AuthPossible;
}
/*}}}*/
+time_t debReleaseIndex::GetNotBefore() const /*{{{*/
+{
+ return d->NotBefore;
+}
+ /*}}}*/
metaIndex * debReleaseIndex::UnloadedClone() const /*{{{*/
{
if (Trusted == TRI_NO)
@@ -685,6 +717,22 @@ bool debReleaseIndex::SetValidUntilMax(time_t const Valid)
return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Max-ValidTime", URI.c_str(), Dist.c_str());
return true;
}
+bool debReleaseIndex::SetCheckDate(TriState const pCheckDate)
+{
+ if (d->CheckDate == TRI_UNSET)
+ d->CheckDate = pCheckDate;
+ else if (d->CheckDate != pCheckDate)
+ return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Check-Date", URI.c_str(), Dist.c_str());
+ return true;
+}
+bool debReleaseIndex::SetDateMaxFuture(time_t const DateMaxFuture)
+{
+ if (d->DateMaxFuture == 0)
+ d->DateMaxFuture = DateMaxFuture;
+ else if (d->DateMaxFuture != DateMaxFuture)
+ return _error->Error(_("Conflicting values set for option %s regarding source %s %s"), "Date-Max-Future", URI.c_str(), Dist.c_str());
+ return true;
+}
bool debReleaseIndex::SetSignedBy(std::string const &pSignedBy)
{
if (SignedBy.empty() == true && pSignedBy.empty() == false)
@@ -1168,9 +1216,11 @@ class APT_HIDDEN debSLTypeDebian : public pkgSourceList::Type /*{{{*/
);
if (Deb->SetTrusted(GetTriStateOption(Options, "trusted")) == false ||
- Deb->SetCheckValidUntil(GetTriStateOption(Options, "check-valid-until")) == false ||
- Deb->SetValidUntilMax(GetTimeOption(Options, "valid-until-max")) == false ||
- Deb->SetValidUntilMin(GetTimeOption(Options, "valid-until-min")) == false)
+ Deb->SetCheckValidUntil(GetTriStateOption(Options, "check-valid-until")) == false ||
+ Deb->SetValidUntilMax(GetTimeOption(Options, "valid-until-max")) == false ||
+ Deb->SetValidUntilMin(GetTimeOption(Options, "valid-until-min")) == false ||
+ Deb->SetCheckDate(GetTriStateOption(Options, "check-date")) == false ||
+ Deb->SetDateMaxFuture(GetTimeOption(Options, "date-max-future")) == false)
return false;
std::map<std::string, std::string>::const_iterator const signedby = Options.find("signed-by");
diff --git a/apt-pkg/deb/debmetaindex.h b/apt-pkg/deb/debmetaindex.h
index 5a97cfc78..864ac3eba 100644
--- a/apt-pkg/deb/debmetaindex.h
+++ b/apt-pkg/deb/debmetaindex.h
@@ -55,6 +55,8 @@ class APT_HIDDEN debReleaseIndex : public metaIndex
bool SetCheckValidUntil(TriState const Trusted);
bool SetValidUntilMin(time_t const Valid);
bool SetValidUntilMax(time_t const Valid);
+ bool SetCheckDate(TriState const CheckDate);
+ bool SetDateMaxFuture(time_t const DateMaxFuture);
bool SetSignedBy(std::string const &SignedBy);
std::map<std::string, std::string> GetReleaseOptions();
@@ -63,6 +65,8 @@ class APT_HIDDEN debReleaseIndex : public metaIndex
bool IsArchitectureAllSupportedFor(IndexTarget const &target) const;
bool HasSupportForComponent(std::string const &component) const;
+ APT_PURE time_t GetNotBefore() const;
+
void AddComponent(std::string const &sourcesEntry,
bool const isSrc, std::string const &Name,
std::vector<std::string> const &Targets,
diff --git a/apt-pkg/metaindex.cc b/apt-pkg/metaindex.cc
index bdae6dcc9..8cbdb5e01 100644
--- a/apt-pkg/metaindex.cc
+++ b/apt-pkg/metaindex.cc
@@ -72,6 +72,13 @@ APT_PURE std::string metaIndex::GetReleaseNotes() const { return d->ReleaseNotes
APT_PURE signed short metaIndex::GetDefaultPin() const { return d->DefaultPin; }
APT_PURE bool metaIndex::GetSupportsAcquireByHash() const { return SupportsAcquireByHash; }
APT_PURE time_t metaIndex::GetValidUntil() const { return ValidUntil; }
+APT_PURE time_t metaIndex::GetNotBefore() const
+{
+ debReleaseIndex const *const deb = dynamic_cast<debReleaseIndex const *>(this);
+ if (deb != nullptr)
+ return deb->GetNotBefore();
+ return 0;
+}
APT_PURE time_t metaIndex::GetDate() const { return this->Date; }
APT_PURE metaIndex::TriState metaIndex::GetLoadedSuccessfully() const { return LoadedSuccessfully; }
APT_PURE std::string metaIndex::GetExpectedDist() const { return Dist; }
diff --git a/apt-pkg/metaindex.h b/apt-pkg/metaindex.h
index 91cfce59b..08664305e 100644
--- a/apt-pkg/metaindex.h
+++ b/apt-pkg/metaindex.h
@@ -82,6 +82,7 @@ public:
bool GetSupportsAcquireByHash() const;
time_t GetValidUntil() const;
time_t GetDate() const;
+ APT_HIDDEN time_t GetNotBefore() const; // FIXME make virtual
std::string GetExpectedDist() const;
bool CheckDist(std::string const &MaybeDist) const;
diff --git a/apt-pkg/sourcelist.cc b/apt-pkg/sourcelist.cc
index fd0264ff1..1fdc92dcb 100644
--- a/apt-pkg/sourcelist.cc
+++ b/apt-pkg/sourcelist.cc
@@ -119,6 +119,8 @@ bool pkgSourceList::Type::ParseStanza(vector<metaIndex *> &List, /*{{{*/
mapping.insert(std::make_pair("Check-Valid-Until", std::make_pair("check-valid-until", false)));
mapping.insert(std::make_pair("Valid-Until-Min", std::make_pair("valid-until-min", false)));
mapping.insert(std::make_pair("Valid-Until-Max", std::make_pair("valid-until-max", false)));
+ mapping.insert(std::make_pair("Check-Date", std::make_pair("check-date", false)));
+ mapping.insert(std::make_pair("Date-Max-Future", std::make_pair("date-max-future", false)));
mapping.insert(std::make_pair("Signed-By", std::make_pair("signed-by", false)));
mapping.insert(std::make_pair("PDiffs", std::make_pair("pdiffs", false)));
mapping.insert(std::make_pair("By-Hash", std::make_pair("by-hash", false)));
diff --git a/debian/NEWS b/debian/NEWS
index 132920b5d..a8cd8f7ad 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,19 @@
+apt (1.6~alpha8) UNRELEASED; urgency=medium
+
+ APT now verifies that the date of Release files is not in the future. By
+ default, it may be 10 seconds in the future to allow for some clock drift.
+
+ Two new configuration options can be used to tweak the behavior:
+ Acquire::Check-Date
+ Acquire::Max-DateFuture
+
+ These can be overridden in sources.list entries using the check-date
+ and date-future-max options. Note that disabling check-date also
+ disables checks on valid-until: It is considered to mean that your
+ machine's time is not reliable.
+
+ -- Julian Andres Klode <juliank@ubuntu.com> Mon, 12 Feb 2018 12:53:18 +0100
+
apt (1.6~alpha1) unstable; urgency=medium
All methods provided by apt except for cdrom, gpgv, and rsh now
diff --git a/doc/apt.conf.5.xml b/doc/apt.conf.5.xml
index fdcd99425..e285b3130 100644
--- a/doc/apt.conf.5.xml
+++ b/doc/apt.conf.5.xml
@@ -315,6 +315,27 @@ APT::Compressor::rev {
for the download itself (see also &sources-list;).</para>
<variablelist>
+ <varlistentry><term><option>Check-Date</option></term>
+ <listitem><para>
+ Security related option defaulting to true, enabling time-related
+ checks. Disabling it means that the machine's time cannot be
+ trusted, and APT will hence disable all time-related checks,
+ such as <option>Check-Valid-Until</option> and verifying that
+ the Date field of a release file is not in the future.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry><term><option>Max-FutureTime</option></term>
+ <listitem><para>Maximum time (in seconds) before its creation (as indicated
+ by the <literal>Date</literal> header) that the <filename>Release</filename>
+ file should be considered valid.
+
+ The default value is <literal>10</literal>.
+ Archive specific settings can be made by appending the label of the archive
+ to the option name. Preferably, the same can be achieved for specific
+ &sources-list; entries by using the <option>Date-Max-Future</option> option there.
+ </para></listitem>
+ </varlistentry>
<varlistentry><term><option>Check-Valid-Until</option></term>
<listitem><para>
Security related option defaulting to true, as giving a Release file's
diff --git a/doc/examples/configure-index b/doc/examples/configure-index
index 9088bd844..8a74fde2d 100644
--- a/doc/examples/configure-index
+++ b/doc/examples/configure-index
@@ -234,6 +234,9 @@ Acquire
Max-ValidTime::* "<INT>"; // repository label specific configuration
Min-ValidTime "<INT>"; // time in seconds
Min-ValidTime::* "<INT>"; // repository label specific configuration
+ Check-Date "<BOOL>"; // whether to check the "Date" field
+ Max-FutureTime "<INT>"; // seconds to allow release file's Date field to be in the future (default 10)
+ Max-FutureTime::* "<INT>"; // repository label specific configuration
SameMirrorForAllIndexes "<BOOL>"; // use the mirror serving the Release file for Packages & co
diff --git a/doc/sources.list.5.xml b/doc/sources.list.5.xml
index 2a047c83b..592227dd8 100644
--- a/doc/sources.list.5.xml
+++ b/doc/sources.list.5.xml
@@ -343,6 +343,23 @@ deb-src [ option1=value1 option2=value2 ] uri suite [component1] [component2] [.
default.
</para></listitem>
+ <listitem><para><option>Check-Date</option> (<option>check-date</option>)
+ is a yes/no value which controls if APT should consider
+ the machine's time correct and hence perform time related
+ checks, such as verifying that a Release file is not
+ from the future. Disabling it also disables the
+ <option>Check-Valid-Until</option> option
+ mentioned above.
+ </para></listitem>
+
+ <listitem><para><option>Date-Max-Future</option>
+ (<option>date-max-future</option>) controls how far
+ from the future a repository may be.
+ Default to the value of the configuration option
+ <option>Acquire::Max-FutureTime</option> which is
+ 10 seconds by default.
+ </para></listitem>
+
<listitem><para><option>InRelease-Path</option> (<option>inrelease-path</option>)
determines the path to the InRelease file, relative
to the normal position of an <filename>InRelease</filename> file.
diff --git a/test/integration/framework b/test/integration/framework
index ecce46d2e..bc84de184 100644
--- a/test/integration/framework
+++ b/test/integration/framework
@@ -460,6 +460,8 @@ EOF
echo "Apt::Cmd::Disable-Script-Warning \"1\";" > rootdir/etc/apt/apt.conf.d/apt-binary
export APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=no
echo 'Acquire::Connect::AddrConfig "false";' > rootdir/etc/apt/apt.conf.d/connect-addrconfig
+ # Allow release files to be 10 hours in the future, rather than 10 seconds
+ echo 'Acquire::Max-FutureTime "'$((10 * 60 * 60))'";' > rootdir/etc/apt/apt.conf.d/future-time
configcompression '.' 'gz' #'bz2' 'lzma' 'xz'
confighashes 'SHA256' # these are tests, not security best-practices
diff --git a/test/integration/test-releasefile-date b/test/integration/test-releasefile-date
new file mode 100755
index 000000000..a98507408
--- /dev/null
+++ b/test/integration/test-releasefile-date
@@ -0,0 +1,46 @@
+#!/bin/sh
+set -e
+
+TESTDIR="$(readlink -f "$(dirname "$0")")"
+. "$TESTDIR/framework"
+setupenvironment
+configarchitecture 'i386'
+
+insertpackage 'wheezy' 'apt' 'all' '0.8.15'
+
+getlabelfromsuite() {
+ echo -n 'Testcases'
+}
+
+setupaptarchive --no-update
+
+runtest() {
+ local MSG="$1"
+ msgtest "Release file is $MSG as it has" "$2"
+ rm -rf rootdir/var/lib/apt/lists
+ generatereleasefiles "$3"
+ signreleasefiles
+ shift 3
+ if [ "$MSG" = 'accepted' ]; then
+ testsuccess --nomsg aptget update "$@"
+ testfailure grep -q 'is not valid yet' rootdir/tmp/testsuccess.output
+ else
+ testfailure --nomsg aptget update "$@"
+ testsuccess grep -q 'is not valid yet' rootdir/tmp/testfailure.output
+ fi
+}
+
+
+runtest 'accepted' 'no date' ''
+runtest 'accepted' 'ok date' 'now + 1 hour'
+runtest 'rejected' 'date to far in the future' 'now + 12 hours'
+runtest 'accepted' 'date to far in the future, but accepted via option' 'now + 12 hours' -o Acquire::Max-FutureTime=86400
+
+sed -i -e 's#\(deb\(-src\)\?\) #\1 [check-date=no] #' rootdir/etc/apt/sources.list.d/*
+runtest 'accepted' 'bad Date but overridden by sources option' 'now + 1 day'
+
+sed -i -e 's#\(deb\(-src\)\?\) \[.*\] #\1 [date-max-future=86400] #' rootdir/etc/apt/sources.list.d/*
+runtest 'accepted' 'Date allowed via sources list option via sources option' 'now + 12 hours'
+
+sed -i -e 's#\(deb\(-src\)\?\) \[.*\] #\1 [date-max-future=86405] #' rootdir/etc/apt/sources.list.d/*
+runtest 'rejected' 'Date further in the future than allowed by sources.list option' 'now + 2 day'