Project

General

Profile

Actions

Idea #6277

closed

[API] Make manifest format validation more strict, to make "munge" methods simpler and safer.

Added by Tom Clegg over 9 years ago. Updated over 9 years ago.

Status:
Resolved
Priority:
Normal
Assigned To:
Radhika Chippada
Category:
API
Target version:
Start date:
06/10/2015
Due date:
Story points:
1.0

Description

A valid manifest format has three kinds of tokens: stream name, block locator, and file.

The current manifest parsing code (including signature validation and generation) has some implicit assumptions that it's working on a valid manifest, but:
  • there are some cases of "check this token just in case the manifest isn't valid" that could be optimized out if we had checked beforehand.
  • there are almost certainly some remaining loopholes with undefined behavior. For example, if a line (invalidly) starts with a block locator, does its signature get checked on the way in? Does it get a new signature on the way out? If the answers are "no" and "yes" respectively, it's a trivial exploit.

Rather than litter the signature code with edge cases covering invalid manifests, we should validate the format before doing any further parsing/munging operations. Subsequent manipulations can safely make the simplifying assumption that the manifest is valid.

The validation method itself should go in the Ruby SDK.

Valid manifest format is described at Keep manifest format.


Subtasks 7 (0 open7 closed)

Task #6287: Review branch: 6277-manifest-validation (ruby sdk updates only)ResolvedRadhika Chippada06/10/2015Actions
Task #6324: Review branch: 6277-manifest-validation-apiResolvedRadhika Chippada06/15/2015Actions
Task #6882: Review branch: 6277-check_manifest_validityResolvedTom Clegg08/04/2015Actions
Task #6488: Fail collection.create/update calls with invalid manifests.ResolvedRadhika Chippada06/10/2015Actions
Task #6487: Summarize invalid manifests present in production servers' databasesResolvedRadhika Chippada06/10/2015Actions
Task #6486: Review production logs to confirm no real clients are submitting any invalid manifestsResolvedRadhika Chippada06/10/2015Actions
Task #6747: Review summarization data (note #22)ResolvedTom Clegg07/22/2015Actions
Actions #1

Updated by Tom Clegg over 9 years ago

  • Description updated (diff)
  • Project changed from 35 to Arvados
  • Category set to API
Actions #3

Updated by Brett Smith over 9 years ago

  • Target version changed from Arvados Future Sprints to 2015-07-08 sprint
Actions #5

Updated by Radhika Chippada over 9 years ago

  • Assigned To set to Radhika Chippada
Actions #6

Updated by Tom Clegg over 9 years ago

  • Description updated (diff)
Actions #7

Updated by Radhika Chippada over 9 years ago

  • Status changed from New to In Progress
Actions #8

Updated by Tom Clegg over 9 years ago

At 0c082b7

None of the "valid" test cases are actually valid: they are all missing a trailing newline.

I think errors will be more readable if we use word.inspect instead of just word:

-Manifest invalid for stream #{line_count}. Invalid file token foo
+Manifest invalid for stream #{line_count}. Invalid file token "foo" 

The last condition has an unnecessary regexp check: if word =~ FILE_REGEXP then we'd still be in the loop. I think it would be more helpful if split into two distinct error reports:

-        if(count == 0) or (word and word !~ FILE_REGEXP)
-          raise ArgumentError.new "Manifest invalid for stream #{line_count}. Missing or invalid file name #{word}" 
-        end
+        if word
+          raise ArgumentError.new "Manifest invalid for stream #{line_count}: invalid #{'file ' if count>0}token #{word.inspect}" 
+        elsif count == 0
+          raise ArgumentError.new "Manifest invalid for stream #{line_count}: no file tokens" 
+        end

(The current version reports "invalid file token" for ". {locator} bogus-string {locator} {file} {file}\n" which seems a bit confusing, hence the "'file ' if count>0" bit above.)

Here are some more valid test cases.

". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/bar.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 000000000000000000000000000000:0777:foo.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:0:0\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:\\040\n" 
". 00000000000000000000000000000000+0 0:0:0\n" 
". 00000000000000000000000000000000+0 0:0:d41d8cd98f00b204e9800998ecf8427e+0+Ad41d8cd98f00b204e9800998ecf8427e00000000@ffffffff\n" 

Invalid:

". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt d41d8cd98f00b204e9800998ecf8427e+0\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:\n" 
". d41d8cd98f00b204e9800998ecf8427e+0\n" 
". 0:0:foo.txt d41d8cd98f00b204e9800998ecf8427e+0\n" 
". 0:0:foo.txt\n" 
".\n" 
"." 
". \n" 
".  \n" 
".\td41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
" . d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt \n" 
". d41d8cd98f00b204e9800998ecf8427e+0  0:0:foo.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n \n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n " 
"\n. d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
" \n. d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:/foo.txt\n" 
"./ d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
".//foo d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
"./foo/ d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
"./foo//bar d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo//bar.txt\n" 
". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo/\n" 
Actions #9

Updated by Radhika Chippada over 9 years ago

Tom:

  • Updated to add more conditions to the manifest format, such as stream names and file names should not contain '//' etc.
  • Add the tests you provided. All but two tests are not working as per your expectation. Of the two unmet tests:
    • ".\td41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n" : here the "\t" is being interpreted as one white space character and hence is being used as split separator
    • ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt \n" : here the space character after foo.txt is used as split seperator
    • Due to this these two tests actually "validate".
  • I was able to get the regexps to work with " should not start with '/' " and " should not end with '/' ". However, I could not get the regexp working for " should not include '//' ". Hence, I am checking this condition separately. Please let me know if have better regular expressions for stream names and file names.

Thanks.

Actions #10

Updated by Tom Clegg over 9 years ago

Doh... From the Ruby docs for split, "If pattern is a single space, str is split on whitespace, with leading whitespace and runs of contiguous whitespace characters ignored." But split(/ /) just plain splits on spaces, which is what we want. (The "ignore empty trailing fields" behavior is undocumented, afaics.)

I've pushed 6277-manifest-validation-TC with the "\t" bug fixed and a bunch more tests. The wiki spec didn't actually say anything about TAB, so I updated it.

I also updated the wiki spec to forbid "." and ".." components in stream and filenames, and added code/tests for those.

Actions #11

Updated by Radhika Chippada over 9 years ago

Tom, thanks for the updates in the -TC branch and also updating the wiki with more clarity.

I ran all tests and saw no test failures.

Merging into master. Thanks.

Actions #12

Updated by Radhika Chippada over 9 years ago

Branch 6277-manifest-validation-api:

Added the check_manifest_validity before_filter in collection model. However, as discussed, this method logs any validation errors and always returns true for the time being.

Actions #13

Updated by Tom Clegg over 9 years ago

Pushed a small adjustment to the collection_test.rb on 6277-manifest-validation-api-TC. The rest LGTM, thanks.

Actions #14

Updated by Tom Clegg over 9 years ago

Presumably we are leaving this ticket open (or moving "check logs for offending clients", "really enable the validation", and "migrate old data" to another ticket) until those things are done.

Actions #15

Updated by Tom Clegg over 9 years ago

One more thing. Can we rename the validation to "log_invalid_manifest_format" so we don't forget it doesn't actually validate?

Actions #16

Updated by Tom Clegg over 9 years ago

  • Story points changed from 0.5 to 1.0
Actions #17

Updated by Brett Smith over 9 years ago

  • Target version changed from 2015-07-08 sprint to 2015-07-22 sprint
Actions #18

Updated by Brett Smith over 9 years ago

  • Target version changed from 2015-07-22 sprint to 2015-08-05 sprint
Actions #19

Updated by Nico César over 9 years ago

I did a:

cd /var/www/qr1hi.arvadosapi.com/current/log
bzgrep -i invalid  production.log-201507*

and found no results related to manifests.

same in su92l

Actions #20

Updated by Radhika Chippada over 9 years ago

  • File manifest_validation_test.rb added
  • File su92l_invalid_manifests added
  • File qr1hi_invalid_manifests added

Reworked on my script to use collection api instead of db lookup and tested in 9tee4, su92l, and qr1hi. Here are the outcomes (we already know manifests not terminating with \n in db are not captured with api lookups):

  • 9tee4
    • total of 1093
    • none are invalid (we do know some manifests not terminating with \n do exist)
  • su92l
    • total of 10476
    • 12 invalid - (and we do not know how many are not terminating with \n) - attached output
  • qr1hi
    • total of 60768
    • of these 52 are invalid - (and we do not know how many are not terminating with \n) - attached output

Next steps:

  • Get the list of collections with manifests not terminating with \n
    • select uuid from collections where manifest_text not like '%\n' and length(manifest_text)>0 and created_at > '2015-07-01';
  • Now that we know there are collections with invalid manifests, how do want to address these collections with invalid manifests?
Actions #21

Updated by Radhika Chippada over 9 years ago

  • File 9tee4-invalid-manifests added

Ran the validation check script against 9tee4 db dump. There are 21 manifests not terminating with \n character and no other invalid manifests.

Actions #22

Updated by Radhika Chippada over 9 years ago

  • File 4xphq_invalid_manifests_db_lookup_output.txt added
  • File 9tee4_invalid_manifests_db_lookup_output.txt added
  • File qr1hi_invalid_manifests_db_lookup_output.txt added
  • File su92l_invalid_manifests_db_lookup_output.txt added

Adding *invalid_manifests_db_lookup_output.txt files for all our environments.

These collection uuids are obtained by directly fetching the collection rows from DB and running the validator on the manifest texts.

The outputs hence include all rows that failed validation including those that do not end with a \n character.

Actions #23

Updated by Radhika Chippada over 9 years ago

  • File deleted (su92l_invalid_manifests)
Actions #24

Updated by Radhika Chippada over 9 years ago

  • File deleted (manifest_validation_test.rb)
Actions #25

Updated by Radhika Chippada over 9 years ago

  • File deleted (qr1hi_invalid_manifests)
Actions #26

Updated by Radhika Chippada over 9 years ago

  • File deleted (9tee4-invalid-manifests)
Actions #27

Updated by Radhika Chippada over 9 years ago

  • File deleted (9tee4_invalid_manifests_db_lookup_output.txt)
Actions #28

Updated by Radhika Chippada over 9 years ago

  • File deleted (4xphq_invalid_manifests_db_lookup_output.txt)
Actions #29

Updated by Radhika Chippada over 9 years ago

  • File deleted (su92l_invalid_manifests_db_lookup_output.txt)
Actions #30

Updated by Radhika Chippada over 9 years ago

  • File deleted (qr1hi_invalid_manifests_db_lookup_output.txt)
Actions #31

Updated by Radhika Chippada over 9 years ago

Moved the summary output files into #6859

Actions #32

Updated by Radhika Chippada over 9 years ago

004243ab59e3a2fc36a708b66373297dd83e0b91 in branch 6277-check_manifest_validity

  • Updated before_validation filter to return false if manifest validation fails
  • Added API controller tests to create collections with invalid manifest
  • Updated one failing fuse test to omit unsupported streams (. and ..)
Actions #33

Updated by Tom Clegg over 9 years ago

We need a unit test, too: the model should prevent invalid manifests from getting into the database ever, not just through collections#create.

In the controller tests, confirm the response includes the appropriate error message.
  • Currently the "nil" case is throwing #<NoMethodError: undefined method `encoding' for nil:NilClass>. (I think we should put self.manifest_text ||= '' somewhere, much like we do with serialized fields, so Collection.new.save gives you an empty collection instead of an error.)
  • The way the exception is swallowed, it looks like the validation error isn't propagated to the client at all right now. We probably want something like
  • self.manifest_text ||= ''
    begin
      Keep::Manifest.validate! manifest_text
    rescue ArgumentError => e
      errors.add :manifest_text, e.message
      return false
    end
    
FUSE tests:
  • Good catch. Could you add a comment to the sanitize_filename function itself in fusedir.py to explain that the '.' and '..' cases are believed to be unreachable if the API server is newer than #6277?
Actions #34

Updated by Radhika Chippada over 9 years ago

27daf08f38eec505c224e7776678b32d50241e13

Addressed all your comments. I also added "default_empty_manifest" before_validation filter and now Collection.new.save also works. Thanks.

Actions #35

Updated by Tom Clegg over 9 years ago

Couple of minor things

The "nil manifest_text" tests don't belong in the "create/update collection with invalid manifest #{manifest_text}" loops -- they test entirely different things so they should just be separate test cases.

Do we expect validate! to raise an exception other than ArgumentError? If not, we should say rescue ArgumentError => e instead of rescue => e.

Actions #36

Updated by Radhika Chippada over 9 years ago

Tom wrote:

"nil manifest_text" tests don't belong in the "create/update collection with invalid manifest ...

Yes, I broke these into separate tests and added tests for a couple more valid manifests.

Do we expect validate! to raise an exception other than ArgumentError?

You are right. We only expect ArgumentError. Updated accordingly.

Thanks.

Actions #37

Updated by Radhika Chippada over 9 years ago

  • Status changed from In Progress to Resolved

Applied in changeset arvados|commit:7e112cd7504e7e379604e2b1fd46b53054a24050.

Actions

Also available in: Atom PDF