* bug#19479: Package manager vulnerable
[not found] <E1Y8Bor-0003yH-Mu@fencepost.gnu.org>
@ 2015-01-06 6:38 ` Kelly Dean
2015-01-07 4:27 ` Richard Stallman
0 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2015-01-06 6:38 UTC (permalink / raw)
To: Richard Stallman; +Cc: 19479
Richard Stallman wrote:
> What do we need to do on ftp.gnu.org to avoid these dangers?
It depends on what you expect the user's responsibility to be.
If you expect him to know the latest version number of a package (without relying on the gnu.org webserver to find out, in case it's compromised), and you expect him to manually verify that his download is the latest version (in addition to verifying the signature, of course), and you give him the ability to do this by always including both the name and the version number in your packages (so far as I'm aware, you already do) and never re-using version numbers (I think you're ok here too), then you have no problem, so there's nothing you need to do.
Otherwise, the problems and solution are the same as for package distribution systems in general, as detailed at
https://www.cs.arizona.edu/stork/packagemanagersecurity/attacks-on-package-managers.html
https://www.cs.arizona.edu/stork/packagemanagersecurity/otherattacks.html
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-06 6:38 ` bug#19479: Package manager vulnerable Kelly Dean
@ 2015-01-07 4:27 ` Richard Stallman
0 siblings, 0 replies; 20+ messages in thread
From: Richard Stallman @ 2015-01-07 4:27 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479
[[[ To any NSA and FBI agents reading my email: please consider ]]]
[[[ whether defending the US Constitution against all enemies, ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]
> If you expect him to know the latest version number of a package
> (without relying on the gnu.org webserver to find out, in case
> it's compromised),
It is normal for users to find the latest version based on gnu.org.
So we don't expect that.
> and you expect him to manually verify that his download is the
> latest version (in addition to verifying the signature, of
> course),
The file name has the version in it.
So it seems we have a problem to fix. Would you like to help
us fix it?
--
Dr Richard Stallman
President, Free Software Foundation
51 Franklin St
Boston MA 02110
USA
www.fsf.org www.gnu.org
Skype: No way! That's nonfree (freedom-denying) software.
Use Ekiga or an ordinary phone call.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: bug#19479: Disclaimer is now on file at FSF
@ 2015-02-24 18:11 Glenn Morris
2015-02-24 23:02 ` Kelly Dean
0 siblings, 1 reply; 20+ messages in thread
From: Glenn Morris @ 2015-02-24 18:11 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479, Emacs developers
So, I don't want to get into this discussion, but I've always assumed
that disclaimers do not/cannot apply to future changes. I asked
assign@gnu.org, and they confirmed "Disclaimers are not valid for future
contributions".
I mention this because AFAICS you are sending new patches.
Your disclaimer is dated 2015-1-8. AFAICS we cannot apply anything after
that. Someone should also check the several patches from you that have
been applied recently to make sure they originated before this date.
Sorry, I don't have time/inclination to discuss special cases.
Maybe you want to take it up with rms and/or assign@gnu.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: bug#19479: Disclaimer is now on file at FSF
2015-02-24 18:11 bug#19479: Disclaimer is now on file at FSF Glenn Morris
@ 2015-02-24 23:02 ` Kelly Dean
2015-02-25 21:09 ` Glenn Morris
0 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2015-02-24 23:02 UTC (permalink / raw)
To: Glenn Morris; +Cc: 19479, emacs-devel
> So, I don't want to get into this discussion,
And yet you chose to dig it back up, even after everybody else was satisfied that it was resolved more than a month ago. The copyright clerk's exact words on January 20th were, ‟We've accepted the public domain disclaimer and added you to our records”, not ‟we've accepted part of the disclaimer, but rejected another part”.
The disclaimer covers future changes, and everybody's comments about that part had already been CCed to the clerk, and his answer was, ‟We've accepted the public domain disclaimer”.
> but I've always assumed
> that disclaimers do not/cannot apply to future changes. I asked
> assign@gnu.org, and they confirmed "Disclaimers are not valid for future
> contributions".
Good luck finding a copyright judge anywhere in America who would agree with your absurd claim that my work since January 8th is not in the public domain, despite my signed statement that it is.
Or if you admit it is PD, I'm sure you can dream up some rationalization of why PD code isn't allowed in Emacs, and then try to remove it all, which is a lot more than just my code.
Either way, I'm done trying to work on Emacs. This B.S. isn't worth my time.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: bug#19479: Disclaimer is now on file at FSF
2015-02-24 23:02 ` Kelly Dean
@ 2015-02-25 21:09 ` Glenn Morris
2017-09-02 12:24 ` Eli Zaretskii
0 siblings, 1 reply; 20+ messages in thread
From: Glenn Morris @ 2015-02-25 21:09 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479, emacs-devel
Kelly Dean wrote:
>> So, I don't want to get into this discussion,
>
> And yet you chose to dig it back up, even after everybody else was
> satisfied that it was resolved more than a month ago.
I've been largely on a break from Emacs. I always thought there was
something strange here, and I just happened to get motivated enough now
to ask assign@gnu for clarification when I saw patches were still arriving.
> The copyright clerk's exact words on January 20th were, ‟We've
> accepted the public domain disclaimer and added you to our records",
> not ‟we've accepted part of the disclaimer, but rejected another
> part".
I specifically mentioned you by name in the question I asked assign@gnu,
and the reply I got (one day ago) was, in totality:
Disclaimers are not valid for future contributions. Thanks for checking in.
Like I said, you can take it up with them if you disagree.
I'be glad to be corrected, but it all seems pretty clear to me.
I am not trying to be the bad guy and I am not out to get you.
I applied several patches from you and would have been happy to apply more.
I am just trying to ensure Emacs follows the FSF's procedures,
which seem pretty clear to me.
Vibhav Pant wrote:
> Well, what about
> http://git.savannah.gnu.org/cgit/gnulib.git/plain/doc/Copyright/request-assign.future?
> This is the form to request documents for assigning past and future
> works, which according to you isn't possible.
That's not what I said.
I said: I am told *disclaimers* cannot apply to future changes.
You will note that there are separate documents for *assigning* past
changes (request-assign.changes), and past-and-future changes
(request-assign.future). But for *disclaimers* there is only
request-disclaim.changes. There is no request-disclaim.future.
If you read
http://git.savannah.gnu.org/cgit/gnulib.git/tree/doc/Copyright/request-disclaim.changes
it quite clearly states that it only applies to past, finished changes.
I am not a lawyer (AFAIK, neither is anyone else on this list) and
have no interest in discussing why these things are different.
They just are.
I said the first time we went through this that it was my
understanding that disclaimers worked this way. I said it again here:
https://lists.gnu.org/archive/html/emacs-devel/2015-01/msg00198.html
I have now had this confirmed by assign@gnu.
If you (the generic you) want to contribute to Emacs, there is a
well-defined, simple procedure that hundreds of people have followed
with no problem.
If you don't want to follow the procedure, then fine, that's your
prerogative. Then you can't contribute.
But please don't start arguing with us about what the procedure is, or
should be, or what you think a judge might say, or why you need to be an
exception. We don't set the rules here at Emacs, and it just isn't a
productive use of anyone's time.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Disclaimer is now on file at FSF
2015-02-25 21:09 ` Glenn Morris
@ 2017-09-02 12:24 ` Eli Zaretskii
0 siblings, 0 replies; 20+ messages in thread
From: Eli Zaretskii @ 2017-09-02 12:24 UTC (permalink / raw)
To: Glenn Morris; +Cc: kelly, 19479, emacs-devel
unblock 24655 by 19479
thanks
> From: Glenn Morris <rgm@gnu.org>
> Date: Wed, 25 Feb 2015 16:09:57 -0500
> Cc: 19479@debbugs.gnu.org, emacs-devel@gnu.org
>
> I am not a lawyer (AFAIK, neither is anyone else on this list) and
> have no interest in discussing why these things are different.
> They just are.
>
> I said the first time we went through this that it was my
> understanding that disclaimers worked this way. I said it again here:
> https://lists.gnu.org/archive/html/emacs-devel/2015-01/msg00198.html
>
> I have now had this confirmed by assign@gnu.
>
>
> If you (the generic you) want to contribute to Emacs, there is a
> well-defined, simple procedure that hundreds of people have followed
> with no problem.
>
> If you don't want to follow the procedure, then fine, that's your
> prerogative. Then you can't contribute.
>
> But please don't start arguing with us about what the procedure is, or
> should be, or what you think a judge might say, or why you need to be an
> exception. We don't set the rules here at Emacs, and it just isn't a
> productive use of anyone's time.
Two and a half years later, with no one complaining about this, it
doesn't sound right for this issue to block the release of Emacs 26.1.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: [PATCH] Re: bug#19479: Package manager vulnerable
2015-01-08 3:31 bug#19479: [PATCH] " Kelly Dean
@ 2015-01-08 3:44 Glenn Morris
2015-01-08 5:29 ` Kelly Dean
0 siblings, 1 reply; 20+ messages in thread
From: Glenn Morris @ 2015-01-08 3:44 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479
I appreciate the spirit of wanting to provide a patch, but unless you
have changed your position on the Emacs copyright assignment, I don't
see that this patch can be used by Emacs.
(Ref: http://debbugs.gnu.org/14492#19)
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-08 3:44 bug#19479: [PATCH] Re: bug#19479: Package manager vulnerable Glenn Morris
@ 2015-01-08 5:29 ` Kelly Dean
2015-01-08 14:39 ` Stefan Monnier
0 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2015-01-08 5:29 UTC (permalink / raw)
To: Glenn Morris; +Cc: 19479
Glenn Morris wrote:
> I appreciate the spirit of wanting to provide a patch, but unless you
> have changed your position on the Emacs copyright assignment, I don't
> see that this patch can be used by Emacs.
I did do what you requested: submit a bug report, but not a patch. But this isn't just a bug; it's a security vulnerability, and Stefan invited me to submit a patch to fix it. So then I did.
Regarding the copyright issue, please don't conflate two separate issues like your copyright clerk tried to.
The first issue is: does the FSF want any more public domain code in Emacs than is already there? The answer is ‟no”, as explained by Donald R Robertson III, your copyright clerk, on February 19, 2013. When explaining why the FSF wouldn't accept my PD code, he wrote, ‟It really is more beneficial for our enforcement efforts if we get the work assigned instead of 'disclaimed'. We will only accept a disclaimer instead of an assignment in particular circumstances.”
Of course, he's right; PD code isn't useful for your enforcement efforts, but it's absurd to say it's an issue for my patches, which even including this latest one, amount to no more than a few parts per million of the Emacs code base. Obviously it doesn't hurt your efforts; no copyright judge is going to care if Emacs has a few lines of Hamlet or any other PD information in it. The judge will let you sue people for GPL violations just the same.
Anyway, the first issue is clear: new PD code is unwelcome in Emacs. Emacs is your project, not mine, so regardless of how silly I think your exclusion of PD code is, I abided (and still abide) by your wishes. I submitted this patch because Stefan invited me to. Maybe Stefan just forgot that you asked me not to submit any more patches, but I assumed he invited this patch because a security vulnerability counted as a ‟particular circumstance” that your copyright clerk mentioned.
The second issue is: is my code in the public domain? The answer is ‟yes”; the author of SQLite says that's PD, and it is, the author of Qmail says that's PD, and it is, and I'm simply doing the same thing they are. My code is in the public domain. If you want, I can PGP-sign and publish on my website a statement that my patches are PD, even though that's more than the authors of SQLite and Qmail deemed necessary for their code.
Your clerk wrote, ‟placing a work in the public domain is difficult/may not be possible”. But that's obviously false, as proven by his statement that you do (sometimes) accept disclaimers, and as proven by the general legal acceptance of other people's statements that their work is PD, including highly respected authors such as Richard Hipp.
It's clear that the second issue is not an issue, especially in the United States, which is where I am, and the only purpose served by the FSF bringing it up is clouding the first issue, which is the only real issue.
I recommend not rejecting a patch to fix a security vulnerability just for the sake of keeping 29 lines of new PD code out of Emacs. If it really is too much PD code, then I recommend deleting feedmail.el (PD) to compensate.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-08 5:29 ` Kelly Dean
@ 2015-01-08 14:39 ` Stefan Monnier
2015-01-08 21:06 ` Kelly Dean
0 siblings, 1 reply; 20+ messages in thread
From: Stefan Monnier @ 2015-01-08 14:39 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479
> of PD code is, I abided (and still abide) by your wishes. I submitted this
> patch because Stefan invited me to. Maybe Stefan just forgot that you asked
> me not to submit any more patches,
Indeed, that's the case. You're one of the very rare oddballs who can't
be bothered to sign a trivial document to get this out of the way, but
for the life of me, I can't remember the names of the handful of
oddballs, so I keep repeating this error.
> but I assumed he invited this patch because a security vulnerability
> counted as a ‟particular circumstance” that your copyright
> clerk mentioned.
Emacs is full of vulnerabilities and has barely started using encryption
technology to try and eliminate some of them, so no, it's definitely not
"special" in this sense. And in any case the "special"ness usually
doesn't refer to the usefulness of the code but rather to the fact that
it'd be difficult to get this code some other way (i.e. it's both
important/useful code and it'd take a while to rewrite it).
Stefan
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: [PATCH] Re: bug#19479: Package manager vulnerable
2015-01-05 2:16 ` Stefan Monnier
@ 2015-01-08 3:31 Kelly Dean
2015-01-08 5:50 ` bug#19536: [PATCH] package-upload-buffer-internal fails for tar files Stefan Monnier
-1 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2015-01-08 3:31 UTC (permalink / raw)
To: Stefan Monnier; +Cc: 19479
[-- Attachment #1: Type: text/plain, Size: 2116 bytes --]
Stefan Monnier wrote:
> > If filenames include version numbers and the version numbers are never
> > reused,
>
> The ELPA system in general does not enforce that. But the GNU ELPA
> scripts do, and other ELPA servers work in a way that should generally
> make sure this is also the case.
But having security rely on that makes it easier than necessary to accidentally open a window of vulnerability by failing to enforce that constraint. It's a brittle solution.
>> then your solution does prevent package replay attacks. Since Emacs
>> packages already include a Version header (and the package name), you could
>> actually do your proposed verification using that header, without changing
>> the way signatures are currently made, which is a solution I addressed in my
>> original emacs-devel message.
>
> Indeed, I realized this just after I sent my message.
> So we can fix this problem simply by changing package.el so as to check
> that the name&version of the downloaded file match the name&version
> contained therein.
> Patch welcome.
Ok, but as I explained in my original message, that solution still makes the attacker's job easier than necessary in some cases. Verifying the hash is a more robust solution than verifying the version number, so my patch below verifies the hash.
This is forward compatible. You can install this now and start putting archive-contents with hashes on elpa (and melpa and marmalade), and old clients will simply ignore the hashes and operate as usual.
BTW, one happy side effect of properly fixing this vulnerability is eliminating melpa's incentive to mangle package version numbers (they're mangled apparently to deal with the problem of package maintainers reusing version numbers).
> It should be fairly easy to add a timestamp in there without
> causing any backward incompatibility.
Unfortunately, I don't see how to add timestamps to archive-contents without breaking old clients, so the metadata replay vulnerability will have to remain open until you decide how to handle the compatibility problem. My patch here only fixes the package replay vulnerability.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: package-replay-vuln.patch --]
[-- Type: text/x-diff, Size: 3425 bytes --]
--- emacs-24.4/lisp/emacs-lisp/package.el
+++ emacs-24.4/lisp/emacs-lisp/package.el
@@ -314,6 +314,11 @@
(defvar package--default-summary "No description available.")
+(defvar package-hashfun 'sha256 "Function for secure hashing.")
+
+(defvar package-acceptable-hashfuns '(sha256)
+ "Past and current `package-hashfun' functions that are still secure.")
+
(cl-defstruct (package-desc
;; Rename the default constructor from `make-package-desc'.
(:constructor package-desc-create)
@@ -843,6 +848,20 @@
(epg-context-result-for context 'verify)))
good-signatures))))
+(defun package--check-size (pkg-desc)
+ (eq (cdr (assoc :size (package-desc-extras pkg-desc)))
+ (pcase (package-desc-kind pkg-desc)
+ (`single (string-bytes (buffer-string)))
+ (`tar (buffer-size)) ; Because insert-file-contents mangled the literal
+ (kind (error "Unknown package kind: %s" kind)))))
+
+(defun package--check-hash (pkg-desc)
+ (let* ((x (cdr (assoc :hash (package-desc-extras pkg-desc))))
+ (hashfun (car x)) ; Avoid Git's shortsightedness
+ (hash (cadr x)))
+ (and (memq hashfun package-acceptable-hashfuns)
+ (string= hash (secure-hash hashfun (current-buffer))))))
+
(defun package-install-from-archive (pkg-desc)
"Download and install a tar package."
(let* ((location (package-archive-base pkg-desc))
@@ -859,6 +878,10 @@
(unless (eq package-check-signature 'allow-unsigned)
(error "Unsigned package: `%s'"
(package-desc-name pkg-desc)))))
+ (unless (package--check-size pkg-desc)
+ (error "File size not correct: %s" (package-desc-name pkg-desc)))
+ (unless (package--check-hash pkg-desc)
+ (error "Failed to verify hash: %s" (package-desc-name pkg-desc)))
(package-unpack pkg-desc))
;; Here the package has been installed successfully, mark it as
;; signed if appropriate.
@@ -1172,7 +1195,10 @@
(package--prepare-dependencies
(package-read-from-string requires-str)))
:kind 'single
- :url homepage))))
+ :url homepage
+ :size (string-bytes (buffer-string))
+ :hash (list package-hashfun
+ (secure-hash package-hashfun (current-buffer)))))))
(declare-function tar-get-file-descriptor "tar-mode" (file))
(declare-function tar--extract "tar-mode" (descriptor))
@@ -1184,7 +1210,10 @@
(let* ((dir-name (file-name-directory
(tar-header-name (car tar-parse-info))))
(desc-file (package--description-file dir-name))
- (tar-desc (tar-get-file-descriptor (concat dir-name desc-file))))
+ (tar-desc (tar-get-file-descriptor (concat dir-name desc-file)))
+ (size (buffer-size tar-data-buffer))
+ (hash (list package-hashfun
+ (secure-hash package-hashfun tar-data-buffer))))
(unless tar-desc
(error "No package descriptor file found"))
(with-current-buffer (tar--extract tar-desc)
@@ -1196,7 +1225,8 @@
(error "Can't find define-package in %s"
(tar-header-name tar-desc))
(apply #'package-desc-from-define
- (append (cdr pkg-def-parsed))))))
+ (append (cdr pkg-def-parsed)
+ (list :size size :hash hash))))))
(setf (package-desc-kind pkg-desc) 'tar)
pkg-desc)
(kill-buffer (current-buffer))))))
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19536: [PATCH] package-upload-buffer-internal fails for tar files
@ 2015-01-08 5:50 ` Stefan Monnier
2015-01-08 11:40 ` bug#19479: Package manager vulnerable Kelly Dean
0 siblings, 1 reply; 20+ messages in thread
From: Stefan Monnier @ 2015-01-08 5:50 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19536
> It creates a file with a ⌜.tar⌝ extension that isn't a valid tar file.
IOW, it's been broken for all these years?
> Since elpa.gnu.org _does_ have valid tar files, I guess somebody
> wrote a script to work around this bug by overwriting the invalid tar files
> with the originals.
We don't use this code. The tarballs are built directly on elpa.gnu.org
from elpa/admin/* scripts.
> I'm submitting a patch for this since it affects validation of my patch for
> bug #19479, and I'm submitting the latter patch because it
> fixes a security vulnerability.
If this function's been broken for so long and nobody reported it, I'd
rather just remove it.
Stefan
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-08 5:50 ` bug#19536: [PATCH] package-upload-buffer-internal fails for tar files Stefan Monnier
@ 2015-01-08 11:40 ` Kelly Dean
0 siblings, 0 replies; 20+ messages in thread
From: Kelly Dean @ 2015-01-08 11:40 UTC (permalink / raw)
To: 19479
BTW, Stefan mentioned (see bug #19536) that you don't use package-x for elpa.gnu.org, and instead use some other scripts, so it just occurred to me that you might not immediately notice that my patch not only verifies hashes, but also generates them, so there's nothing extra you need to do.
Just use package-upload-file from package-x.el, and it will automatically add the appropriate entry (including hash) for the package to the archive-contents file.
Apply the fix for bug #19536 if you want package-upload-file to correctly add tar files to the archive's package directory. (It already correctly adds single-file packages.)
GNU elpa, Melpa, and Marmalade can start using the new archive-contents now. Old clients will still work fine, and simply ignore the hashes. New clients will verify them.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Emacs package manager vulnerable to replay attacks
@ 2014-12-30 10:42 Kelly Dean
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
0 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2014-12-30 10:42 UTC (permalink / raw)
To: emacs-devel
Remove your ~/.emacs.d/elpa/ directory, and start 24.4.
Use the default of '(("gnu" . "http://elpa.gnu.org/packages/")) for package-archives.
Do M-x list-packages, which will download the archive metadata (http://elpa.gnu.org/packages/archive-contents and its sig file), verify the sig, and generate archive-contents.signed which indicates successful verification.
Then:
mkdir /tmp/localarch; cd /tmp/localarch
cp ~/.emacs.d/elpa/archives/gnu/* . # archive-contents and archive-contents.signed
wget http://elpa.gnu.org/packages/undo-tree-0.6.5.el # Currently latest in elpa
wget http://elpa.gnu.org/packages/undo-tree-0.6.5.el.sig
Now in Emacs, do (setq package-archives '(("gnu" . "/tmp/localarch/")))
Undo-tree 0.6.7 doesn't exist yet, but let's pretend that it does, and it fixes a hypothetical security vulnerability in 0.6.5, and the update has been uploaded to elpa. (Skipping 0.6.6 for this example since it does exist, just not in elpa.)
To simulate this, edit /tmp/localarch/archive-contents, find the entry for undo-tree, and change the (0 6 5) to (0 6 7). Your modified file will no longer verify with http://elpa.gnu.org/packages/archive-contents.sig, but that's ok since you already have /tmp/localarch/archive-contents.signed.
Now, to simulate a package replay attack, rename undo-tree-0.6.5.el to undo-tree-0.6.7.el, and undo-tree-0.6.5.el.sig to undo-tree-0.6.7.el.sig. Then do M-x list-packages again, then install undo-tree 0.6.7. It installs successfully, meaning you've tricked Emacs into installing the vulnerable version of the package even though archive-contents lists the fixed version.
For a live attack, first retrieve all packages and their sig files from elpa, then wait for a vulnerability to be discovered and fixed in one of them, e.g. undo-tree 0.6.5.
Allow your victim to run list-packages to download the new archive-contents (which lists the fixed undo-tree 0.6.7) and to verify it using the new archive-contents.sig.
Then when he tries to download undo-tree-0.6.7.el and undo-tree-0.6.7.el.sig, intercept his connection and give him the content of undo-tree-0.6.5.el and undo-tree-0.6.5.el.sig, so that his Emacs saves the old content using the new names.
His Emacs will successfully verify the signature because it is authentic; you retrieved authentic (but now stale) packages and signatures in your first step. His Emacs will then install the stale package because the version number embedded in the filename matches the version number specified in the new archive-contents file.
The attack also works for already-installed packages when new versions are published; you can deliver stale data when the victim tries to upgrade.
To solve the problem above, simply include a hash of the package content in the package's record in archive-contents, rather than only including the package name and version number in that file as Emacs currently does. This solution also happens to make per-package signatures from the elpa key superfluous, so you can remove that feature from Emacs and remove those signature files from elpa for the sake of simplicity, and use the elpa key only to sign the archive-contents file.
(Of course, per-commit (not just per-package) signatures from authors would still be useful, so that users don't have to trust the elpa keyholder alone, but that's a separate issue. Emacs should barf if a package hash doesn't verify, regardless of whether any signatures verify.)
Package replay attacks could be prevented without putting hashes in archive-contents, by instead verifying that the version number listed in archive-contents matches the version number listed in the package itself, if version numbers were never reused for different versions of packages. But there is at least one package (undo-tree) that does re-use version numbers, so hashes are still necessary. Hashes also prevent elpa itself from reusing version numbers; forcing elpa to issue a new archive-contents whenever it lists a new version of any package makes it more conspicuous that something changed, and making the historical record clear closes off one particular method of attack if the elpa key is compromised.
After the problem of package replay attacks is solved, you can still attack by replaying not only packages, but also the metadata (i.e. the archive-contents and archive-contents.sig files). To solve this problem, include a timestamp of archive-contents in that file itself (so that the signature depends on the timestamp), and have Emacs ignore any new archive-contents that's older than the latest valid one that Emacs has already seen or is older than some specified limit (IIRC Debian's apt-get uses a 10-day limit).
For details on replay attacks, see the 2008 publication
https://www.cs.arizona.edu/stork/packagemanagersecurity/attacks-on-package-managers.html
Another attack is the endless-data attack; I haven't checked whether Emacs is vulnerable. See
https://www.cs.arizona.edu/stork/packagemanagersecurity/otherattacks.html
But since Emacs signs its repository metadata, it appears it is not vulnerable to any of the other attacks described on that page.
In addition to recording hashes, also record the length of the content; it's convenient for early detection of endless-data attacks and of misconfigurations.
One final feature that isn't necessary for preventing any of the vulnerabilities above, but still is helpful to make the historical record even more clear, is to include in each version of archive-contents a hash (and length) of the previous version of that file. This further constrains an attacker who has compromised the elpa key; he can still launch attacks, but it's harder to keep the attacks secret for very long, since he's forced to cause a fork in what's supposed to be a linear hash chain.
Fortunately, all four of these features (package hashes, content length, archive timestamps, and archive hash chaining) are straightforward to implement.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2014-12-30 10:42 Emacs package manager vulnerable to replay attacks Kelly Dean
@ 2015-01-01 12:38 ` Kelly Dean
2015-01-04 20:00 ` Stefan Monnier
` (3 more replies)
0 siblings, 4 replies; 20+ messages in thread
From: Kelly Dean @ 2015-01-01 12:38 UTC (permalink / raw)
To: 19479
Ivan Shmakov requested that I send this message to the bug list.
For details, see my message with subject ⌜Emacs package manager vulnerable to replay attacks⌝ to emacs-devel on 30 Dec 2014:
https://lists.gnu.org/archive/html/emacs-devel/2014-12/msg02319.html
Executive summary to fix the vulnerabilities:
0. Include a hash and length of each package's content in the package's record in archive-contents, rather than only including the package name and version number in that file as Emacs currently does. Barf if a package hash doesn't verify, regardless of whether any signatures verify.
(Length technically not necessary, but still generally useful, e.g. if there's a length mismatch then you know there's a content mismatch and you don't have to bother checking the hash.)
Stop distributing elpa-key signatures of packages, since they're superfluous if you have package hashes in archive-contents and have elpa-key signatures of archive-contents, and you already have the latter.
1. Include a timestamp of archive-contents in that file itself (so that the signature in archive-contents.sig depends on the timestamp, so that the timestamp can't be forged), and have Emacs ignore any new archive-contents that's older than the latest valid one that Emacs has already seen or is older than some specified limit. One thing I forgot to mention in my original message: have Emacs signal a warning if it ever sees an archive-contents dated in the future, which indicates misconfiguration of the client or server (or of course, some kind of mischief).
Optional alternative timestamp handling, as Ivan pointed out that Debian does (at least sometimes): Instead of expiring archive-contents after some limit configured in Emacs, put an explicit expiration date in it. Personally, I don't like server-supplied expiration dates, kind of for a similar reason that RMS doesn't like server-supplied Javascript, or maybe just because I have too many irritating memories of expired SSL certs.
Ivan suggested maybe filing those as separate bug reports, but it's pointless to fix either of them unless both are fixed, so it makes more sense to include them together.
One more feature: include in each version of archive-contents a hash (and length) of the previous version of that file. This isn't necessary for preventing any of the vulnerabilities above, but it's easy insurance that slightly mitigates the disaster if the metadata signing key is compromised. It's pointless unless both the above problems are fixed, so it makes sense to put it here.
BTW, check whether Emacs is vulnerable to endless-data attack. (I haven't.) If it is, then the length field mentioned above (which is a good idea in any case) will assist in early detection of this attack. This belongs here because... well no it doesn't, but I don't want to file a separate bug report for it because the report would be bogus if it turns out Emacs isn't vulnerable, and I've already filled my bogusness quota for the week.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
@ 2015-01-04 20:00 ` Stefan Monnier
2015-01-05 1:11 ` Kelly Dean
2017-09-03 1:10 ` Glenn Morris
` (2 subsequent siblings)
3 siblings, 1 reply; 20+ messages in thread
From: Stefan Monnier @ 2015-01-04 20:00 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479
> For details, see my message with subject ⌜Emacs package manager vulnerable
> to replay attacks⌝ to emacs-devel on 30 Dec 2014:
> https://lists.gnu.org/archive/html/emacs-devel/2014-12/msg02319.html
AFAICT, this vulnerability also applies to the way GNU packages are
distributed in ftp.gnu.org (i.e. as a tarball plus a .sig file).
Is that right?
> Executive summary to fix the vulnerabilities:
Another way to attack the problem is to include the file name along with
its content in "the thing that gets signed".
I.e. the signature shouldn't apply to the output of "cat <foo>" but to
the output of "echo <foo>; cat <foo>".
This way an attacker can't take <vulnerable>.tar along with
<vulnerable>.tar.sig and send them off as <safe>.tar along with
<safe>.tar.sig.
Stefan
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-04 20:00 ` Stefan Monnier
@ 2015-01-05 1:11 ` Kelly Dean
2015-01-05 2:16 ` Stefan Monnier
0 siblings, 1 reply; 20+ messages in thread
From: Kelly Dean @ 2015-01-05 1:11 UTC (permalink / raw)
To: Stefan Monnier; +Cc: 19479
Stefan Monnier wrote:
> AFAICT, this vulnerability also applies to the way GNU packages are
> distributed in ftp.gnu.org (i.e. as a tarball plus a .sig file).
>
> Is that right?
Yes, because there are no hashes in, or signatures on, http://ftp.gnu.org/find.txt.gz or http://ftp.gnu.org/ls-lrRt.txt.gz
They used to do it right; see
http://ftp.gnu.org/before-2003-08-01.md5sums.asc
But it looks like they stopped.
Having to redo a huge monolithic metadata file whenever any data file changes is inefficient; it's more efficient for the metadata for each directory to just have the hash of each file in the directory and the hash of the metadata of each subdirectory, like Git does. But either way will prevent package replay attacks.
>> Executive summary to fix the vulnerabilities:
>
> Another way to attack the problem is to include the file name along with
> its content in "the thing that gets signed".
> I.e. the signature shouldn't apply to the output of "cat <foo>" but to
> the output of "echo <foo>; cat <foo>".
>
> This way an attacker can't take <vulnerable>.tar along with
> <vulnerable>.tar.sig and send them off as <safe>.tar along with
> <safe>.tar.sig.
If filenames include version numbers and the version numbers are never reused, then your solution does prevent package replay attacks. Since Emacs packages already include a Version header (and the package name), you could actually do your proposed verification using that header, without changing the way signatures are currently made, which is a solution I addressed in my original emacs-devel message.
But having a list of hashes of all the packages (and even better, chaining together all the versions of that list) makes changes to any package more conspicuous, which makes the attacker's job harder, as I explained. And if you do that, then the elpa key no longer needs to sign individual packages at all. Git, Fossil, and Debian's apt-get use hash lists, and Git and Fossil also chain together the lists, so there's good precedence. Both are simple to do for Emacs: in the archive-contents file, include the hash of each package and the hash of the previous version of archive-contents.
But remember, none of the above prevents metadata replay attacks. If the user himself is specifying the metadata (e.g. you manually request Emacs 24.4 because you know that's the latest version), then verification to prevent metadata replay attacks isn't the computer's job. But when the user just says to update some package(s) to the latest version, without specifying the version, then it is the computer's job. For this, put a timestamp of the archive-contents file into the file itself.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-05 1:11 ` Kelly Dean
@ 2015-01-05 2:16 ` Stefan Monnier
0 siblings, 0 replies; 20+ messages in thread
From: Stefan Monnier @ 2015-01-05 2:16 UTC (permalink / raw)
To: Kelly Dean; +Cc: 19479
> If filenames include version numbers and the version numbers are never
> reused,
The ELPA system in general does not enforce that. But the GNU ELPA
scripts do, and other ELPA servers work in a way that should generally
make sure this is also the case.
> then your solution does prevent package replay attacks. Since Emacs
> packages already include a Version header (and the package name), you could
> actually do your proposed verification using that header, without changing
> the way signatures are currently made, which is a solution I addressed in my
> original emacs-devel message.
Indeed, I realized this just after I sent my message.
So we can fix this problem simply by changing package.el so as to check
that the name&version of the downloaded file match the name&version
contained therein.
Patch welcome.
> But remember, none of the above prevents metadata replay attacks. If the
> user himself is specifying the metadata (e.g. you manually request Emacs
> 24.4 because you know that's the latest version), then verification to
> prevent metadata replay attacks isn't the computer's job. But when the user
> just says to update some package(s) to the latest version, without
> specifying the version, then it is the computer's job. For this,
> put a timestamp of the archive-contents file into the file itself.
Agreed. It should be fairly easy to add a timestamp in there without
causing any backward incompatibility.
Stefan
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
2015-01-04 20:00 ` Stefan Monnier
@ 2017-09-03 1:10 ` Glenn Morris
2019-10-04 9:49 ` Stefan Kangas
2020-09-07 17:19 ` Stefan Kangas
3 siblings, 0 replies; 20+ messages in thread
From: Glenn Morris @ 2017-09-03 1:10 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 19479
[Dropping emacs-devel, since that seems unlikely to be productive given
the lack of context.]
Eli Zaretskii wrote:
> Two and a half years later, with no one complaining about this, it
> doesn't sound right for this issue to block the release of Emacs 26.1.
The context here was security vulnerabilities in the package manager.
Personally I'm uneasy with saying "we've ignored this for X years so
let's continue to ignore it.". But I don't have anything substantive to
add.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
2015-01-04 20:00 ` Stefan Monnier
2017-09-03 1:10 ` Glenn Morris
@ 2019-10-04 9:49 ` Stefan Kangas
2020-05-06 0:55 ` Noam Postavsky
2020-09-07 17:19 ` Stefan Kangas
3 siblings, 1 reply; 20+ messages in thread
From: Stefan Kangas @ 2019-10-04 9:49 UTC (permalink / raw)
To: 19479
[-- Attachment #1: Type: text/plain, Size: 4472 bytes --]
Kelly Dean <kelly@prtime.org> writes:
> Ivan Shmakov requested that I send this message to the bug list.
>
> For details, see my message with subject ⌜Emacs package manager vulnerable to replay attacks⌝ to emacs-devel on 30 Dec 2014:
> https://lists.gnu.org/archive/html/emacs-devel/2014-12/msg02319.html
>
> Executive summary to fix the vulnerabilities:
>
> 0. Include a hash and length of each package's content in the package's record
> in archive-contents, rather than only including the package name and version
> number in that file as Emacs currently does. Barf if a package hash doesn't
> verify, regardless of whether any signatures verify.
> (Length technically not necessary, but still generally useful, e.g. if
> there's a length mismatch then you know there's a content mismatch and
> you don't have to bother checking the hash.)
I have implemented the first part of the protection against metadata
replay attacks in the attached patch: support for checksum (or hash)
verification. This change is backwards-compatible; the new fields can
be added to "archive-contents" file without impacting old clients.
I've not yet updated documentation, NEWS, etc. but will get to that
next.
I introduce a new user option `package-verify-checksums' that controls
this new behaviour. The default is 'allow-missing', which only
carries out this check if there are checksums in "archive-contents",
and does nothing otherwise. In itself, this does nothing to protect
against metadata replay attacks (but might protect against data
corruption). You need to set `package-verify-checksums' to t, and
implement timestamping (discussed below).
I still suggest to stick with this default for Emacs 27.1, or at least
until common package archives can catch up. Once this is implemented
in GNU ELPA and MELPA, it makes more sense to move to a stricter
default. Otherwise, the transition will be very bumpy. I therefore
suggest to discuss stricter defaults later.
(BTW, I didn't bother fixing the package-x.el code for this patch,
since it seems like it's not that widely used. It will work as
before, but lack support for adding the checksums automatically.)
> 1. Include a timestamp of archive-contents in that file itself (so that the
> signature in archive-contents.sig depends on the timestamp, so that the
> timestamp can't be forged), and have Emacs ignore any new archive-contents
> that's older than the latest valid one that Emacs has already seen or is older
> than some specified limit. One thing I forgot to mention in my original message:
> have Emacs signal a warning if it ever sees an archive-contents dated in the
> future, which indicates misconfiguration of the client or server (or of course,
> some kind of mischief).
To protect against metadata replay attacks, it is correct that we need
timestamps too. I haven't done that in this first patch, but I hope
to do it in a following patch. I wanted to get this first part done
before I started working on that.
My current best idea for how to do it is one which AFAICT haven't been
raised in this thread before: to add a comment with an RFC3339
timestamp to the top of the "archive-contents" file:
;; Last-Updated: 2019-10-01T15:32:55.000Z
This will be ignored by older versions of Emacs, since package.el uses
(read (current-buffer)) to read this file. New versions will have
an easy time parsing this header, caching the value, and refusing to
update the package cache if the timestamp is older than one we have
already seen. With that, we would have implemented protection
against metadata replay attacks.
I think it would be highly beneficial if this could go in before Emacs
27, not least so that package archives can start implementing support
for this.
Comments on all this are obviously more than welcome.
Best regards,
Stefan Kangas
PS. Note that the original thread ended up highly off-topic discussing
copyright issues, because one potential contributor refused to sign
the standard copyright assignment. The eventual outcome was that we
could not use a patch written by that person. I have therefore
deliberately not looked at that persons patch in order to avoid any
copyright issues. I have implemented this from scratch based solely
on the below link, and the discussion in this thread:
https://www2.cs.arizona.edu/stork/packagemanagersecurity/attacks-on-package-managers.html
[-- Attachment #2: 0001-Support-package-checksum-verification.patch --]
[-- Type: text/x-patch, Size: 29457 bytes --]
From 543029f4d3dcc4e0401263c93e01fe9319395708 Mon Sep 17 00:00:00 2001
From: Stefan Kangas <stefankangas@gmail.com>
Date: Fri, 4 Oct 2019 10:36:14 +0200
Subject: [PATCH] Support package checksum verification
This is the first step towards protecting users of package.el against
metadata replay attacks.
* lisp/emacs-lisp/package.el (package-verify-checksums): New
defcustom.
(package-desc, package--ac-desc)
(package--add-to-archive-contents, package-install-from-archive): New
fields 'size' and 'checksums'.
(package-desc-filename): New function.
(package-process-define-package): Doc fix.
(bad-checksum): New error type.
(package--verify-package-checksum)
(package--verify-package-size): New function to verify that the
checksum and size of a package corresponds to the checksum and size
data in the "archive-contents" file on the package archive.
(package--show-verify-checksum-error): New function to show
details of an error on checksum verification. (Bug#19479)
* lisp/emacs-lisp/package-x.el (package-upload-buffer-internal):
Update to use above new fields 'size' and 'checksums'.
* test/lisp/emacs-lisp/package-tests.el (package-test-refresh-contents)
(package-test-install-single-from-archive): Update tests.
(with-install-using-checksum): New macro.
(package-test-install-with-checksum/single-valid)
(package-test-install-with-checksum/single-invalid)
(package-test-install-with-checksum/tar-valid)
(package-test-install-with-checksum/tar-invalid): New tests for
installing packages with checksums.
(package-test-verification-text)
(package-tests-valid-md5-checksum)
(package-tests-valid-sha256-checksum)
(package-tests-valid-sha512-checksum): New variables.
(run-verify-checksums-test): New macro.
(package-test--verify-package-checksums-nil/ignore-invalid)
(package-test--verify-package-checksums-allow-missing)
(package-test--verify-package-checksums-allow-missing/missing)
(package-test--verify-package-checksums-allow-missing/ignore-unsupported)
(package-test--verify-package-checksums-t)
(package-test--verify-package-checksums-t/invalid-fails)
(package-test--verify-package-checksums-t/missing-fails)
(package-test--verify-package-checksums-all)
(package-test--verify-package-checksums-all/invalid-fails)
(package-test--verify-package-checksums-all/missing-fails)
(package-test--verify-package-checksums-all/no-supported-hash-fails)
(package-test--verify-package-checksums-all/ignore-unsupported)
(package-test--verify-package-size): New tests for the checksum
support.
* test/lisp/emacs-lisp/package-resources/archive-contents:
* test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el:
* test/lisp/emacs-lisp/package-resources/checksum-valid-123.el:
* test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar:
* test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar:
New test data files.
---
lisp/emacs-lisp/package-x.el | 4 +-
lisp/emacs-lisp/package.el | 159 ++++++++++++++--
.../package-resources/archive-contents | 26 ++-
.../package-resources/checksum-invalid-1.0.el | 17 ++
.../checksum-invalid-tar-0.1.tar | Bin 0 -> 10240 bytes
.../package-resources/checksum-valid-123.el | 17 ++
.../checksum-valid-tar-0.99.tar | Bin 0 -> 10240 bytes
test/lisp/emacs-lisp/package-tests.el | 177 +++++++++++++++++-
8 files changed, 378 insertions(+), 22 deletions(-)
create mode 100644 test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el
create mode 100644 test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar
create mode 100644 test/lisp/emacs-lisp/package-resources/checksum-valid-123.el
create mode 100644 test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar
diff --git a/lisp/emacs-lisp/package-x.el b/lisp/emacs-lisp/package-x.el
index 2815be3fe6..56373c14c4 100644
--- a/lisp/emacs-lisp/package-x.el
+++ b/lisp/emacs-lisp/package-x.el
@@ -219,7 +219,9 @@ package-upload-buffer-internal
(let ((contents (or (package--archive-contents-from-url archive-url)
(package--archive-contents-from-file)))
(new-desc (package-make-ac-desc
- split-version requires desc file-type extras)))
+ split-version requires desc file-type extras
+ ;; FIXME: Use better values than nil nil.
+ nil nil)))
(if (> (car contents) package-archive-version)
(error "Unrecognized archive version %d" (car contents)))
(let ((elt (assq pkg-name (cdr contents))))
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 188f398a56..6795e17ac3 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -335,6 +335,30 @@ package-gnupghome-dir
:risky t
:version "26.1")
+(defcustom package-verify-checksums 'allow-missing
+ "Non-nil means to verify the checksum of a package before installing it.
+
+This can be one of:
+- nil: Ignore checksums.
+- `allow-missing': Same as t if a checksum exists, but install a
+ package even if there is no checksum.
+- t: Require a valid checksum; refuse to install package if the
+ checksum is missing or invalid. Verifies one checksum.
+- `all': Same as t, but verify all available (and supported)
+ checksums.
+
+The package checksums are automatically fetched from package
+archives with the package data on `package-refresh-contents'.
+
+Note that setting this to nil is intended for debugging, and
+should normally not be used since it will decrease security."
+ :type '(choice (const nil :tag "Never")
+ (const allow-missing :tag "Allow missing")
+ (const t :tag "Require valid checksum")
+ (const t :tag "Require valid checksum, and check all"))
+ :risky t
+ :version "27.1")
+
(defcustom package-check-signature 'allow-unsigned
"Non-nil means to check package signatures when installing.
More specifically the value can be:
@@ -429,6 +453,8 @@ package--default-summary
requirements)))
(kind (plist-get rest-plist :kind))
(archive (plist-get rest-plist :archive))
+ (checksums (plist-get rest-plist :checksums))
+ (size (plist-get rest-plist :size))
(extras (let (alist)
(while rest-plist
(unless (memq (car rest-plist) '(:kind :archive))
@@ -466,6 +492,13 @@ package--default-summary
`extras' Optional alist of additional keyword-value pairs.
+`size' Size of the package in bytes.
+
+`checksums' Checksums for the package file. Alist of ((ALGORITHM
+ . CHECKSUM)) where ALGORITHM is a symbol specifying a
+ `secure-hash' algorithm, and CHECKSUM is a string
+ containing the checksum.
+
`signed' Flag to indicate that the package is signed by provider."
name
version
@@ -475,7 +508,9 @@ package--default-summary
archive
dir
extras
- signed)
+ signed
+ size
+ checksums)
(defun package--from-builtin (bi-desc)
"Create a `package-desc' object from BI-DESC.
@@ -538,6 +573,13 @@ package-desc-suffix
('dir "")
(kind (error "Unknown package kind: %s" kind))))
+(defun package-desc-filename (pkg-desc)
+ "Return file-name of package-desc object PKG-DESC.
+This is the concatenation of `package-desc-full-name' and
+`package-desc-suffix'."
+ (concat (package-desc-full-name pkg-desc)
+ (package-desc-suffix pkg-desc)))
+
(defun package-desc--keywords (pkg-desc)
"Return keywords of package-desc object PKG-DESC.
These keywords come from the foo-pkg.el file, and in general
@@ -603,11 +645,11 @@ package-activated-list
(defun package-process-define-package (exp)
"Process define-package expression EXP and push it to `package-alist'.
EXP should be a form read from a foo-pkg.el file.
-Convert EXP into a `package-desc' object using the
-`package-desc-from-define' constructor before pushing it to
-`package-alist'.
-If there already exists a package by that name in
-`package-alist', replace that definition with the new one."
+
+Convert EXP into a `package-desc' object, then push it to
+`package-alist'. If there already exists a package by the same
+name in `package-alist', add the object to the list of packages,
+and sort the entries by version."
(when (eq (car-safe exp) 'define-package)
(let* ((new-pkg-desc (apply #'package-desc-from-define (cdr exp)))
(name (package-desc-name new-pkg-desc))
@@ -1310,6 +1352,81 @@ package--with-response-buffer-1
url))
(insert-file-contents-literally url)))))
+(define-error 'bad-checksum "Failed to verify checksum")
+
+(defun package--show-verify-checksum-error (pkg-desc details)
+ "Show error on failed checksum verification of PKG-DESC with DETAILS.
+Error is displayed in a new buffer named \"*Error*\"."
+ (with-output-to-temp-buffer "*Error*"
+ (with-current-buffer standard-output
+ (insert (format "Failed to verify checksum of package `%s':\n\n"
+ (package-desc-name pkg-desc)))
+ (insert details))))
+
+(defun package--verify-package-checksum (pkg-desc)
+ "Verify checksums of `package-desc' object PKG-DESC.
+This assumes that the we are in a buffer containing package.
+
+The value of `package-verify-checksums' decides what this
+function does:
+- nil: Do nothing.
+- 'allow-missing: Verify checksum if it exists, otherwise do
+ nothing.
+- t: Verify that there is at least one valid checksum.
+- 'all': Like t, but check all (supported) checksums in turn.
+
+Signal an error of type `bad-checksum' if the verification."
+ (cl-flet*
+ ((supported-hashes
+ (lambda ()
+ (or (seq-filter (lambda (h) (memql (car h) (secure-hash-algorithms)))
+ (package-desc-checksums pkg-desc))
+ ;; Failed; signal error.
+ (package--show-verify-checksum-error
+ pkg-desc
+ (concat
+ (if (package-desc-checksums pkg-desc)
+ (concat
+ "No supported checksums found\n\n"
+ (format-message "Package archive had: %s\n"
+ (package-desc-checksums pkg-desc))
+ (format-message "Emacs supports: %s\n"
+ (secure-hash-algorithms)))
+ "Package archive had no checksums for this package\n")))
+ (signal 'bad-checksum "no supported checksums found"))))
+ (do-check
+ (lambda (&optional all)
+ (dolist (hash (seq-take (supported-hashes)
+ (if all most-positive-fixnum 1)))
+ (let* ((algorithm (car hash))
+ (a (cdr hash))
+ (b (secure-hash algorithm (current-buffer))))
+ (if (equal a b) t
+ ;; Failed; signal error.
+ (package--show-verify-checksum-error
+ pkg-desc
+ (concat
+ (format-message "\nChecksum mismatch (%s)\n\n" algorithm)
+ (format-message "Expected: %s\n" a)
+ (format-message "Result: %s\n" b)))
+ (signal 'bad-checksum (list "checksum mismatch" a b))))))))
+ (pcase package-verify-checksums
+ ('nil nil)
+ ('allow-missing (when (package-desc-checksums pkg-desc) (do-check)))
+ ('t (do-check))
+ ('all (do-check 'all))
+ (_ (user-error "Value of `package-verify-checksums' is invalid: `%s'"
+ package-verify-checksums)))))
+
+(defun package--verify-package-size (pkg-desc)
+ "Verify package size of `package-desc' object PKG-DESC.
+This assumes that the we are in a buffer containing package."
+ (when-let ((a (package-desc-size pkg-desc))
+ (b (string-bytes (buffer-string))))
+ (unless (equal a b)
+ (error "Mismatch in size in package `%s'. Size was %s, but expected %s."
+ (package-desc-name pkg-desc) b a))))
+
(define-error 'bad-signature "Failed to verify signature")
(defun package--check-signature-content (content string &optional sig-file)
@@ -1437,14 +1554,19 @@ package--add-to-compatibility-table
(version-list-< table-version version))
(puthash name version package--compatibility-table)))))
-;; Package descriptor objects used inside the "archive-contents" file.
-;; Changing this defstruct implies changing the format of the
-;; "archive-contents" files.
(cl-defstruct (package--ac-desc
- (:constructor package-make-ac-desc (version reqs summary kind extras))
+ (:constructor
+ package-make-ac-desc (version reqs summary kind extras size checksums))
(:copier nil)
(:type vector))
- version reqs summary kind extras)
+ "Package descriptor object used inside the \"archive-contents\" file.
+Changing this defstruct implies changing the format of the
+\"archive-contents\" files.
+
+This is mainly used in `package--add-to-archive-contents' to make
+the code that parses the \"archive-contents\" file more
+readable."
+ version reqs summary kind extras size checksums)
(defun package--append-to-alist (pkg-desc alist)
"Append an entry for PKG-DESC to the start of ALIST and return it.
@@ -1482,10 +1604,14 @@ package--add-to-archive-contents
:summary (package--ac-desc-summary (cdr package))
:kind (package--ac-desc-kind (cdr package))
:archive archive
+ ;; Older "archive-contents" files might not have the
+ ;; below elements.
:extras (and (> (length (cdr package)) 4)
- ;; Older archive-contents files have only 4
- ;; elements here.
- (package--ac-desc-extras (cdr package)))))
+ (package--ac-desc-extras (cdr package)))
+ :size (and (> (length (cdr package)) 5)
+ (package--ac-desc-size (cdr package)))
+ :checksums (and (> (length (cdr package)) 6)
+ (package--ac-desc-checksums (cdr package)))))
(pinned-to-archive (assoc name package-pinned-packages)))
;; Skip entirely if pinned to another archive.
(when (not (and pinned-to-archive
@@ -1956,9 +2082,10 @@ package-install-from-archive
(when (eq (package-desc-kind pkg-desc) 'dir)
(error "Can't install directory package from archive"))
(let* ((location (package-archive-base pkg-desc))
- (file (concat (package-desc-full-name pkg-desc)
- (package-desc-suffix pkg-desc))))
+ (file (package-desc-filename pkg-desc)))
(package--with-response-buffer location :file file
+ (package--verify-package-size pkg-desc)
+ (package--verify-package-checksum pkg-desc)
(if (or (not (package-check-signature))
(member (package-desc-archive pkg-desc)
package-unsigned-archives))
diff --git a/test/lisp/emacs-lisp/package-resources/archive-contents b/test/lisp/emacs-lisp/package-resources/archive-contents
index e2f92304f8..b41287ae89 100644
--- a/test/lisp/emacs-lisp/package-resources/archive-contents
+++ b/test/lisp/emacs-lisp/package-resources/archive-contents
@@ -14,4 +14,28 @@
(multi-file .
[(0 2 3)
nil "Example of a multi-file tar package" tar
- ((:url . "http://puddles.li"))]))
+ ((:url . "http://puddles.li"))])
+ (checksum-valid .
+ [(123)
+ nil "A single-file package with a valid checksum." single
+ nil
+ 343
+ ((sha512 . "6270d64d63c90ef541c3386c40acb7716865499c506465f2ff9b408f05c6612fb0c58f5d83b60af9d7f8d972796ee270d0bc6ca8a17bd0412cc249dede6f7359"))])
+ (checksum-valid-tar .
+ [(0 99)
+ nil "A multi-file package with a valid checksum." tar
+ nil
+ 10240
+ ((sha512 . "2be7c37a16db32a2b08fc917ed5f4241814e2665bda1bd15328c2e5a842e45b81f6f31274697248ffaabf8010796685acb3342c5920af53ddd1e75d7fd764bd1"))])
+ (checksum-invalid .
+ [(1 0)
+ nil "A single-file package with an invalid checksum." single
+ nil
+ 365
+ ((sha512 . "not-a-valid-checksum"))])
+ (checksum-invalid-tar .
+ [(0 1)
+ nil "A multi-file package with an invalid checksum." tar
+ nil
+ 10240
+ ((sha512 . "not-a-valid-checksum"))]))
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el b/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el
new file mode 100644
index 0000000000..3b8b07a4b8
--- /dev/null
+++ b/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el
@@ -0,0 +1,17 @@
+;;; invalid-checksum.el --- A package with an invalid checksum in archive-contents
+
+;; Version: 1.0
+
+;;; Commentary:
+
+;; This package has an invalid checksum in archive-contents and is
+;; just used to verify that package.el refuses to install.
+
+;;; Code:
+
+(defun p-equal-to-np-p ()
+ (error "FIXME"))
+
+(provide 'invalid-checksum)
+
+;;; invalid-checksum.el ends here
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar b/test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar
new file mode 100644
index 0000000000000000000000000000000000000000..f153eb366b1264c293327c08c507876943572533
GIT binary patch
literal 10240
zcmeIwv2KGf5C&k+d5YUQL2a<ZGi2`zoWOvEV=8PS<?U;v3>}Ipq70S#-{Rry!~TBS
z(Y8}uuZ0UY_O2@uFNG}CyLes6T#YdzFRC%}`?|HZ5~?=Zn5I@|Eu=D)R)WmyuCPC8
zjrqkyB2F9zj=LLw>+c@?+l_WF|DPJA_0PO!3;3*at~>dwx_RWUO|2>+D`grfNIvti
zqi6nk{vV^Ib`Hsg6lv}$jV{tBw-XPRy4l9?mgveU*`+*P62);|eMdbzPwW@V-JLkm
z%`UFLyD;PddEn!xDo;n#z<vlo00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf
TKmY;|fB*y_009U<;DEpvqoiQ<
literal 0
HcmV?d00001
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el b/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el
new file mode 100644
index 0000000000..9611fd8c87
--- /dev/null
+++ b/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el
@@ -0,0 +1,17 @@
+;;; valid-checksum.el --- A package with an valid checksum in archive-contents
+
+;; Version: 123
+
+;;; Commentary:
+
+;; This package has an valid checksum in archive-contents and is
+;; used to verify that package.el installs it.
+
+;;; Code:
+
+(defun p-equal-to-np-p ()
+ (error "FIXME"))
+
+(provide 'valid-checksum)
+
+;;; valid-checksum.el ends here
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar b/test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar
new file mode 100644
index 0000000000000000000000000000000000000000..e468754bb94d96a047df7a5c5926eb1d065e363f
GIT binary patch
literal 10240
zcmeHLU60y25aoG)#V9WYX#)vMsLEDX;_ihOX+IFKZTHb616Z5bxwZr4-`{cg=w1O^
zwc3?h)e#~P+cV>HX3iMm1;&rM$owTsdEy(U{Gk5sU8C}XS3uX>D5}scd>aK%?{>&u
zmGB~JMNvC!h3!fZMnM=<AbP<VPMRt-?HQn=ADNgleRAIS#!oK%wFlx8{EvFwe{Om&
z6T2n2-D*AMeU}Gzh`5gS{8JuS{@d+l1%j7x|Gbqyod3y!YoHf{DO2cAr9ce|S&|{l
zcuH?lfmt9NCJN*%eq?j3pFNMT8~ue5IHYx>|A)`(nEvEGY>hut|IOf{wXgq+uvrP3
zAvFwF|33-$4=uaAygX9c#5MT7552D}%Si;}j0EB^CBU}MtqPo-k)<n{uN9v!3{<ab
z&<w&R^c5nE<;<)|U!X4wkubxhIER6V>*cE9Mn^hFJXoWjP-$Aw0edhh7nGt^suL+!
z&XlnMez?7dUdCd*F}nY1)oo^j(Ayw7u$BeHOpwkcTpPuwg+bs3m`EBVcbQE1Y;9fB
zGl~~C3TRMe+Iq5bXw82>fr_OtET$=s;hM)NGy<cT>=V7f1g51OyW%tu$Z1@`a<fmk
z*!kERJO`<4FjZ!3<O*bN0jyEDYe*S|wOe{*=ifG%02gKG3z;K*AZxU}6;D%`A`}{D
zMS5=i5E}4#F!^|QKY{M;1AOj|M%~-V!zE2N3rVB6#EmIV*}-X-#I0h&tSNG9;ifmb
z`bbN<e-Ew)lLv|)`h&@BZ#;(n-3Yoc?2aabtNZKj2!{8g;oZ1b2N)Aa1cWv447#=-
zWs*`ULBn{uW&42`)f8Krz=AE2n2nJyDKbi1%E=v~r|nv=ER|wjZt_Vo0Ssm!q&Wvo
z@x5OdlXn`8)oN9ri=r;oyg}Ss-gV=z5`S9-S%!jYW8L|du9Sr~3o=G5l&QN-B=4}S
zyj@N0?IlQ-stHjfAoua#f~CHZzR3L?s<ktKDk??5c>Tebo1Ryz(d=BwB~K9E96F%k
z({+y`(Lni#uC4!&lK!5P50m3)|IZ)iuh##AX3*aEe>x0o`ak{tbLaop&nYV^T%f=0
z;&4=sJllCa@cwrk$cof(zm&2k$AFD|mVYMf+qnmVrzBFHLZ#qs6*MoBBt>0MxmR;a
y^ZG<P4-Nr`fJ49`;1F;KI0PI54grUNL%<>65O4@M1RMem0f&G?z#;HQA@Cm&a2WFd
literal 0
HcmV?d00001
diff --git a/test/lisp/emacs-lisp/package-tests.el b/test/lisp/emacs-lisp/package-tests.el
index f450fd27c2..823b68b234 100644
--- a/test/lisp/emacs-lisp/package-tests.el
+++ b/test/lisp/emacs-lisp/package-tests.el
@@ -43,6 +43,9 @@
(setq package-menu-async nil)
+;; Silence byte-compiler.
+(defvar epg-config--program-alist)
+
(defvar package-test-user-dir nil
"Directory to use for installing packages during testing.")
@@ -296,14 +299,15 @@ package-test-refresh-contents
(with-package-test ()
(package-initialize)
(package-refresh-contents)
- (should (eq 4 (length package-archive-contents)))))
+ (should (eq 8 (length package-archive-contents)))))
(ert-deftest package-test-install-single-from-archive ()
"Install a single package from a package archive."
(with-package-test ()
(package-initialize)
(package-refresh-contents)
- (package-install 'simple-single)))
+ (package-install 'simple-single)
+ (should (package-installed-p 'simple-single))))
(ert-deftest package-test-install-prioritized ()
"Install a lower version from a higher-prioritized archive."
@@ -557,6 +561,167 @@ package-test-signed
"Status: Installed in ['`‘]signed-good-1.0/['’]."
nil t))))))
+\f
+;;; Tests for package checksum verification.
+
+(defmacro with-install-using-checksum (ok fail package &rest body)
+ "Test installing PACKAGE while setting `package-verify-checksums'."
+ (declare (indent 2))
+ `(progn
+ (dolist (opt ,ok)
+ (let ((package-verify-checksums opt))
+ (with-package-test ()
+ (package-initialize)
+ (package-refresh-contents)
+ (package-install ,package)
+ (package-installed-p ,package))))
+ (dolist (opt ,fail)
+ (let ((package-verify-checksums opt))
+ (should-error
+ (with-package-test ()
+ (package-initialize)
+ (package-refresh-contents)
+ (package-install ,package))
+ :type 'bad-checksum)))))
+
+(ert-deftest package-test-install-with-checksum/single-valid ()
+ "Install a single package with valid checksum."
+ (with-install-using-checksum '(nil allow-missing t all) '() 'checksum-valid))
+
+(ert-deftest package-test-install-with-checksum/single-invalid ()
+ "Install a tar package with invalid checksum."
+ (with-install-using-checksum '(nil) '(allow-missing t all) 'checksum-invalid))
+
+(ert-deftest package-test-install-with-checksum/tar-valid ()
+ "Install a tar package with valid checksum."
+ (with-install-using-checksum '(nil allow-missing t all) '() 'checksum-valid-tar))
+
+(ert-deftest package-test-install-with-checksum/tar-invalid ()
+ "Install a tar package with invalid checksum."
+ (with-install-using-checksum '(nil) '(allow-missing t all) 'checksum-invalid-tar))
+
+(defconst package-test-verification-text
+ "Example text for testing checksum verification.")
+(defconst package-tests-valid-md5-checksum
+ ;; (secure-hash 'md5 package-test-verification-text)
+ "abe6375809e532f081b808b3aa052dfb")
+(defconst package-tests-valid-sha256-checksum
+ ;; (secure-hash 'sha256 package-test-verification-text)
+ "6875aa4523e45ddef627b4edf1296f1d7dd0c22ddd6a6584f0228215d25eefcd")
+(defconst package-tests-valid-sha512-checksum
+ ;; (secure-hash 'sha512 package-test-verification-text)
+ (concat "bdc631f9e675b1ea34570f0a4bb44568dc5cecac905eea737f5f451bc52fd0c6"
+ "81b0d8b3dc2a942b9950fbe9096ebdf517668245c9b5a7bbdea8487a8f9cdce6"))
+
+(defmacro run-verify-checksums-test (verify-checksums checksums)
+ "Run a test for `package-verify-checksums'."
+ (declare (indent 1))
+ `(with-temp-buffer
+ (insert package-test-verification-text)
+ (let ((package-verify-checksums ,verify-checksums)
+ (pkg (package-desc-create :name 'foobar
+ :version '(1 0)
+ :summary "Just a package with checksum."
+ :kind 'single
+ :checksums ,checksums)))
+ (package--verify-package-checksum pkg))))
+
+(ert-deftest package-test--verify-package-checksums-nil/ignore-invalid ()
+ "Ignore all checksums even when invalid."
+ (run-verify-checksums-test nil
+ '((sha512 . "invalid")
+ (invalid . "invalid"))))
+
+(ert-deftest package-test--verify-package-checksums-nil/ignore-empty ()
+ "Ignore all checksums even when empty."
+ (run-verify-checksums-test nil
+ nil))
+
+(ert-deftest package-test--verify-package-checksums-allow-missing ()
+ "Verify checksums (allow-missing) -- verify if available."
+ (run-verify-checksums-test 'allow-missing
+ `((sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test--verify-package-checksums-allow-missing/missing ()
+ "Verify checksums (allow-missing) -- allow missing."
+ (run-verify-checksums-test 'allow-missing
+ nil))
+
+(ert-deftest package-test--verify-package-checksums-allow-missing/ignore-unsupported ()
+ "Verify checksums (t) -- ignore unsupported algorithm."
+ (run-verify-checksums-test 'allow-missing
+ `((ignore . "not supported")
+ (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test--verify-package-checksums-t ()
+ "Verify checksums (t) -- succeed when valid."
+ (run-verify-checksums-test t
+ `((sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test--verify-package-checksums-t/invalid-fails ()
+ "Verify checksums (t) -- fail on invalid."
+ (should-error
+ (run-verify-checksums-test t
+ '((sha512 . "invalid")))
+ :type 'bad-checksum))
+
+(ert-deftest package-test--verify-package-checksums-t/missing-fails ()
+ "Verify checksums (t) -- fail on missing."
+ (should-error
+ (run-verify-checksums-test t
+ nil)
+ :type 'bad-checksum))
+
+(ert-deftest package-test--verify-package-checksums-t/ignore-unsupported ()
+ "Verify checksums (t) -- ignore unsupported algorithm."
+ (run-verify-checksums-test t
+ `((ignore . "not supported")
+ (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test--verify-package-checksums-all ()
+ "Verify checksums (all) -- succeed on valid."
+ (run-verify-checksums-test 'all
+ `((md5 . ,package-tests-valid-md5-checksum)
+ (sha256 . ,package-tests-valid-sha256-checksum)
+ (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test--verify-package-checksums-all/invalid-fails ()
+ "Verify checksums (all) -- fail if one checksum is invalid."
+ (should-error
+ (run-verify-checksums-test 'all
+ `((md5 . ,package-tests-valid-md5-checksum)
+ (sha256 . "invalid")
+ (sha512 . ,package-tests-valid-sha512-checksum)))
+ :type 'bad-checksum))
+
+(ert-deftest package-test--verify-package-checksums-all/missing-fails ()
+ "Verify checksums (all) -- fail on missing checksums."
+ (should-error
+ (run-verify-checksums-test 'all
+ nil)
+ :type 'bad-checksum))
+
+(ert-deftest package-test--verify-package-checksums-all/no-supported-hash-fails ()
+ "Verify checksums (all) -- fail if we have no supported hash."
+ (should-error
+ (run-verify-checksums-test 'all
+ '((unsupported . "invalid")))
+ :type 'bad-checksum))
+
+(ert-deftest package-test--verify-package-checksums-all/ignore-unsupported ()
+ "Verify checksums (all) -- succed if one hash algorithm is unsupported.
+If the rest succeed, just ignore the unsupported one."
+ (run-verify-checksums-test 'all
+ `((md5 . ,package-tests-valid-md5-checksum)
+ (sha256 . ,package-tests-valid-sha256-checksum)
+ (sha512 . ,package-tests-valid-sha512-checksum)
+ (ignore . "not supported"))))
+
+(ert-deftest package-test--verify-package-size ()
+ (with-temp-buffer
+ (let ((len (1+ (abs (random 1000)))))
+ (insert (make-string len ?#))
+ (package--verify-package-size (package-desc-create :size len)))))
\f
;;; Tests for package-x features.
@@ -570,7 +735,9 @@ package-x-test--single-archive-entry-1-3
'single
'((:authors ("J. R. Hacker" . "jrh@example.com"))
(:maintainer "J. R. Hacker" . "jrh@example.com")
- (:url . "http://doodles.au"))))
+ (:url . "http://doodles.au"))
+ nil
+ nil))
"Expected contents of the archive entry from the \"simple-single\" package.")
(defvar package-x-test--single-archive-entry-1-4
@@ -579,7 +746,9 @@ package-x-test--single-archive-entry-1-4
"A single-file package with no dependencies"
'single
'((:authors ("J. R. Hacker" . "jrh@example.com"))
- (:maintainer "J. R. Hacker" . "jrh@example.com"))))
+ (:maintainer "J. R. Hacker" . "jrh@example.com"))
+ nil
+ nil))
"Expected contents of the archive entry from the updated \"simple-single\" package.")
(ert-deftest package-x-test-upload-buffer ()
--
2.20.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2019-10-04 9:49 ` Stefan Kangas
@ 2020-05-06 0:55 ` Noam Postavsky
2020-09-06 23:59 ` Stefan Kangas
0 siblings, 1 reply; 20+ messages in thread
From: Noam Postavsky @ 2020-05-06 0:55 UTC (permalink / raw)
To: Stefan Kangas; +Cc: 19479
Stefan Kangas <stefan@marxist.se> writes:
> Subject: [PATCH] Support package checksum verification
>
> This is the first step towards protecting users of package.el against
> metadata replay attacks.
> +(define-error 'bad-checksum "Failed to verify checksum")
Would it be useful to have bad-signature and this one share a parent?
(by the way, I kind of wonder why it's not called
package-bad-signature).
> + (cl-flet*
> + ((supported-hashes
> + (lambda ()
Is this a function (rather than a variable) just so it can be in the
same cl-flet* as do-check?
> + (or (seq-filter (lambda (h) (memql (car h) (secure-hash-algorithms)))
The list returned by secure-hash-algorithms includes sha1 and md5. This
is a problem if we're going to rely on signing them. We should at least
plan to have some way of filtering out some functions.
> + (a (cdr hash))
> + (b (secure-hash algorithm (current-buffer))))
> + (when-let ((a (package-desc-size pkg-desc))
> + (b (string-bytes (buffer-string))))
I risk descending into trivial nitpicking, but I think 'a' and 'b' are
bit too generic. Something like 'expected' and 'actual' would make it
harder to mix them up.
> +(defmacro run-verify-checksums-test (verify-checksums checksums)
> + "Run a test for `package-verify-checksums'."
> +(ert-deftest package-test--verify-package-checksums-nil/ignore-invalid ()
I think run-verify-checksums-test should be prefixed with package-test
(whereas the individual test names could be prefixed with just package).
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2020-05-06 0:55 ` Noam Postavsky
@ 2020-09-06 23:59 ` Stefan Kangas
2020-09-07 14:14 ` Noam Postavsky
0 siblings, 1 reply; 20+ messages in thread
From: Stefan Kangas @ 2020-09-06 23:59 UTC (permalink / raw)
To: Noam Postavsky; +Cc: 19479
Noam Postavsky <npostavs@gmail.com> writes:
> Stefan Kangas <stefan@marxist.se> writes:
>
>> Subject: [PATCH] Support package checksum verification
>>
>> This is the first step towards protecting users of package.el against
>> metadata replay attacks.
>
>> +(define-error 'bad-checksum "Failed to verify checksum")
>
> Would it be useful to have bad-signature and this one share a parent?
> (by the way, I kind of wonder why it's not called
> package-bad-signature).
Indeed, I fixed that.
>> + (cl-flet*
>> + ((supported-hashes
>> + (lambda ()
>
> Is this a function (rather than a variable) just so it can be in the
> same cl-flet* as do-check?
I'm not sure I understand; it should be a function instead of a variable
because there is logic in there to match `(secure-hash-algorithms)'
against `(package-desc-checksums pkg-desc)' and signal an error.
>> + (or (seq-filter (lambda (h) (memql (car h) (secure-hash-algorithms)))
>
> The list returned by secure-hash-algorithms includes sha1 and md5. This
> is a problem if we're going to rely on signing them. We should at least
> plan to have some way of filtering out some functions.
Yes, we currently would place the onus on the package archives to not
use those algorithms. We could choose to filter them out as completely
unacceptable, I think that makes sense.
>> + (a (cdr hash))
>> + (b (secure-hash algorithm (current-buffer))))
>
>> + (when-let ((a (package-desc-size pkg-desc))
>> + (b (string-bytes (buffer-string))))
>
> I risk descending into trivial nitpicking, but I think 'a' and 'b' are
> bit too generic. Something like 'expected' and 'actual' would make it
> harder to mix them up.
Thanks, fixed.
>> +(defmacro run-verify-checksums-test (verify-checksums checksums)
>> + "Run a test for `package-verify-checksums'."
>
>> +(ert-deftest package-test--verify-package-checksums-nil/ignore-invalid ()
>
> I think run-verify-checksums-test should be prefixed with package-test
> (whereas the individual test names could be prefixed with just package).
That's true. Fixed.
Thanks for the review!
Best regards,
Stefan Kangas
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2020-09-06 23:59 ` Stefan Kangas
@ 2020-09-07 14:14 ` Noam Postavsky
2020-09-07 18:11 ` Stefan Kangas
0 siblings, 1 reply; 20+ messages in thread
From: Noam Postavsky @ 2020-09-07 14:14 UTC (permalink / raw)
To: Stefan Kangas; +Cc: 19479
Stefan Kangas <stefan@marxist.se> writes:
>> Is this a function (rather than a variable) just so it can be in the
>> same cl-flet* as do-check?
>
> I'm not sure I understand; it should be a function instead of a variable
> because there is logic in there to match `(secure-hash-algorithms)'
> against `(package-desc-checksums pkg-desc)' and signal an error.
Ah, I think had forgotten about/was confused by cl-flet's (FUNC (lambda
ARGLIST ...)) syntax when I wrote that. Although I suppose you could
make it a plain variable by moving it inside do-check's lambda (not sure
if that's an improvement)?
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2020-09-07 14:14 ` Noam Postavsky
@ 2020-09-07 18:11 ` Stefan Kangas
0 siblings, 0 replies; 20+ messages in thread
From: Stefan Kangas @ 2020-09-07 18:11 UTC (permalink / raw)
To: Noam Postavsky; +Cc: 19479
Noam Postavsky <npostavs@gmail.com> writes:
> Stefan Kangas <stefan@marxist.se> writes:
>
>>> Is this a function (rather than a variable) just so it can be in the
>>> same cl-flet* as do-check?
>>
>> I'm not sure I understand; it should be a function instead of a variable
>> because there is logic in there to match `(secure-hash-algorithms)'
>> against `(package-desc-checksums pkg-desc)' and signal an error.
>
> Ah, I think had forgotten about/was confused by cl-flet's (FUNC (lambda
> ARGLIST ...)) syntax when I wrote that. Although I suppose you could
> make it a plain variable by moving it inside do-check's lambda (not sure
> if that's an improvement)?
Sure, you could do that. I guess it's mostly down to style, but I
personally feel like that change would make the code a little bit harder
to read here.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
` (2 preceding siblings ...)
2019-10-04 9:49 ` Stefan Kangas
@ 2020-09-07 17:19 ` Stefan Kangas
2020-09-07 23:54 ` Noam Postavsky
3 siblings, 1 reply; 20+ messages in thread
From: Stefan Kangas @ 2020-09-07 17:19 UTC (permalink / raw)
To: 19479
Kelly Dean <kelly@prtime.org> writes:
> Stop distributing elpa-key signatures of packages, since they're
> superfluous if you have package hashes in archive-contents and have
> elpa-key signatures of archive-contents, and you already have the
> latter.
I disagree with this part.
We should continue signing packages _at least_ until such a time that
there is likely to be zero users left who are using an Emacs version
without support for checking package hashes.
> Optional alternative timestamp handling, as Ivan pointed out that
> Debian does (at least sometimes): Instead of expiring archive-contents
> after some limit configured in Emacs, put an explicit expiration date
> in it. Personally, I don't like server-supplied expiration dates, kind
> of for a similar reason that RMS doesn't like server-supplied
> Javascript, or maybe just because I have too many irritating memories
> of expired SSL certs.
Is there any reason not to support both? Package archives could decide
if they want to use this functionality or not, as could users.
> One more feature: include in each version of archive-contents a hash (and
> length) of the previous version of that file. This isn't necessary for
> preventing any of the vulnerabilities above, but it's easy insurance that
> slightly mitigates the disaster if the metadata signing key is compromised. It's
> pointless unless both the above problems are fixed, so it makes sense to put it
> here.
Does anyone understand how this would improve security in our case?
AFAIU, it can help with APT since they support distributing package
metadata in several files. ELPA uses only one file, so I'm not sure it
would make much of a difference?
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2020-09-07 17:19 ` Stefan Kangas
@ 2020-09-07 23:54 ` Noam Postavsky
2020-09-08 8:10 ` Stefan Kangas
0 siblings, 1 reply; 20+ messages in thread
From: Noam Postavsky @ 2020-09-07 23:54 UTC (permalink / raw)
To: Stefan Kangas; +Cc: 19479
Stefan Kangas <stefan@marxist.se> writes:
>
>> One more feature: include in each version of archive-contents a hash
[...]
> Does anyone understand how this would improve security in our case?
> AFAIU, it can help with APT since they support distributing package
> metadata in several files. ELPA uses only one file, so I'm not sure it
> would make much of a difference?
Not entirely, but there's a bit more detail on the emacs-devel thread
linked from the OP:
One final feature that isn't necessary for preventing any of the
vulnerabilities above, but still is helpful to make the historical record even
more clear, is to include in each version of archive-contents a hash (and
length) of the previous version of that file. This further constrains an
attacker who has compromised the elpa key; he can still launch attacks, but
it's harder to keep the attacks secret for very long, since he's forced to
cause a fork in what's supposed to be a linear hash chain.
I think the idea is that if the attacker has the signing key and sends
out a bad version of archive-contents, it will be revealed as soon as
the victim gets a "good" version, since its previous-version hash won't
match. Except that only works if the user can expect to get all
versions of archive-contents, so maybe I've missed something.
^ permalink raw reply [flat|nested] 20+ messages in thread
* bug#19479: Package manager vulnerable
2020-09-07 23:54 ` Noam Postavsky
@ 2020-09-08 8:10 ` Stefan Kangas
0 siblings, 0 replies; 20+ messages in thread
From: Stefan Kangas @ 2020-09-08 8:10 UTC (permalink / raw)
To: Noam Postavsky; +Cc: 19479
Noam Postavsky <npostavs@gmail.com> writes:
> I think the idea is that if the attacker has the signing key and sends
> out a bad version of archive-contents, it will be revealed as soon as
> the victim gets a "good" version, since its previous-version hash won't
> match.
Yes, this is what I understood to be the case as well.
> Except that only works if the user can expect to get all versions of
> archive-contents, so maybe I've missed something.
Exactly my point. So we can't rely on it to bail out if the hashes
don't match up, I think.
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2020-09-08 8:10 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <E1Y8Bor-0003yH-Mu@fencepost.gnu.org>
2015-01-06 6:38 ` bug#19479: Package manager vulnerable Kelly Dean
2015-01-07 4:27 ` Richard Stallman
2015-02-24 18:11 bug#19479: Disclaimer is now on file at FSF Glenn Morris
2015-02-24 23:02 ` Kelly Dean
2015-02-25 21:09 ` Glenn Morris
2017-09-02 12:24 ` Eli Zaretskii
-- strict thread matches above, loose matches on Subject: below --
2015-01-08 3:44 bug#19479: [PATCH] Re: bug#19479: Package manager vulnerable Glenn Morris
2015-01-08 5:29 ` Kelly Dean
2015-01-08 14:39 ` Stefan Monnier
2015-01-08 21:06 ` Kelly Dean
2015-01-09 2:37 ` Stefan Monnier
2015-01-08 3:31 bug#19479: [PATCH] " Kelly Dean
2015-01-08 5:50 ` bug#19536: [PATCH] package-upload-buffer-internal fails for tar files Stefan Monnier
2015-01-08 11:40 ` bug#19479: Package manager vulnerable Kelly Dean
2014-12-30 10:42 Emacs package manager vulnerable to replay attacks Kelly Dean
2015-01-01 12:38 ` bug#19479: Package manager vulnerable Kelly Dean
2015-01-04 20:00 ` Stefan Monnier
2015-01-05 1:11 ` Kelly Dean
2015-01-05 2:16 ` Stefan Monnier
2017-09-03 1:10 ` Glenn Morris
2019-10-04 9:49 ` Stefan Kangas
2020-05-06 0:55 ` Noam Postavsky
2020-09-06 23:59 ` Stefan Kangas
2020-09-07 14:14 ` Noam Postavsky
2020-09-07 18:11 ` Stefan Kangas
2020-09-07 17:19 ` Stefan Kangas
2020-09-07 23:54 ` Noam Postavsky
2020-09-08 8:10 ` Stefan Kangas
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.