-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[http] sanitise URL options before use them #22186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1289,8 +1289,8 @@ Bool_t TRootSniffer::ProduceXml(const std::string &/* path */, const std::string | |
|
|
||
| TString TRootSniffer::DecodeUrlOptionValue(const char *value, Bool_t remove_quotes) | ||
| { | ||
| if (!value || (strlen(value) == 0)) | ||
| return TString(); | ||
| if (!value || !*value) | ||
| return ""; | ||
|
|
||
| TString res = value; | ||
|
|
||
|
|
@@ -1303,13 +1303,40 @@ TString TRootSniffer::DecodeUrlOptionValue(const char *value, Bool_t remove_quot | |
| res.ReplaceAll("%5D", "]"); | ||
| res.ReplaceAll("%3D", "="); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This goes over the string 8 times, whereas we could do all of this in 1 pass...(this is in addition to the TString constructor, which also does a full scan of the string to determine the length).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deny of access or deny of service is already problematic with pure
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still a very suboptimal way to do this validation that scales superlinearly with the arguments' length. But if this is not a concern for this class, fine for me. |
||
|
|
||
| if (remove_quotes && (res.Length() > 1) && ((res[0] == '\'') || (res[0] == '\"')) && | ||
| (res[0] == res[res.Length() - 1])) { | ||
| Char_t quote = 0; | ||
|
|
||
| if ((res.Length() > 1) && ((res[0] == '\'') || (res[0] == '\"')) && (res[0] == res[res.Length() - 1])) | ||
| quote = res[0]; | ||
|
|
||
| // first remove quotes | ||
| if (quote) { | ||
| res.Remove(res.Length() - 1); | ||
| res.Remove(0, 1); | ||
| } | ||
|
|
||
| return res; | ||
| // we expect normal content here, no special symbols, no unescaped quotes | ||
| TString clean; | ||
| for (Ssiz_t i = 0; i < res.Length(); ++i) { | ||
| char c = res[i]; | ||
| if (c == '"' || c == '\\') { | ||
| // escape quotes and slahes | ||
| clean.Append('\\'); | ||
| clean.Append(c); | ||
| } else if (!std::iscntrl(c)) | ||
| // ignore all special symbols | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably I just will use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what this excludes exactly (does it exclude all ASCII non-printable characters? Does it depend on the locale? The man page makes it look like the case), so I was actually more comfortable with the explicit check. |
||
| clean.Append(c); | ||
| } | ||
|
|
||
| if (quote && !remove_quotes) { | ||
| // return string with quotes - when desired | ||
| res = ""; | ||
| res.Append(quote); | ||
| res.Append(clean); | ||
| res.Append(quote); | ||
| return res; | ||
| } | ||
|
|
||
| return clean; | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -416,7 +416,7 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const | |
| if (gDebug > 1) | ||
| Info("TRootSniffer", "Crate IMAGE from object %s", obj->GetName()); | ||
|
|
||
| Int_t width(300), height(200); | ||
| Int_t width = 300, height = 200; | ||
| TString drawopt; | ||
|
|
||
| if (!options.empty()) { | ||
|
|
@@ -429,9 +429,7 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const | |
| Int_t h = url.GetIntValueFromOptions("h"); | ||
| if (h > 10) | ||
| height = h; | ||
| const char *opt = url.GetValueFromOptions("opt"); | ||
| if (opt) | ||
| drawopt = opt; | ||
| drawopt = DecodeUrlOptionValue(url.GetValueFromOptions("opt"), kTRUE); | ||
| } | ||
|
|
||
| Bool_t isbatch = gROOT->IsBatch(); | ||
|
|
@@ -513,6 +511,42 @@ Bool_t TRootSnifferFull::ProduceXml(const std::string &path, const std::string & | |
| return !res.empty(); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////// | ||
| /// Verify argument value | ||
| /// Remove special symbols, escape quotes | ||
| /// If not string - only digits, literals, "+", "-", "." are allowed | ||
|
|
||
| TString TRootSnifferFull::SanitizeArgument(Bool_t isstr, const char *value, Bool_t onlyquotes) | ||
| { | ||
| Int_t beg = 0, end = strlen(value); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Value created from URL and will not be longer than 16K
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above: we should put our own limit as well |
||
| if (end > 0 && isstr && value[beg] == '\"') | ||
| beg++; | ||
| if (end > 0 && isstr && value[end-1] == '\"') | ||
| end--; | ||
| TString sanitized; | ||
| if (isstr) | ||
| sanitized.Append("\""); // string start with quotes | ||
| for(Int_t n = beg; n < end; ++n) { | ||
| Char_t c = value[n]; | ||
| if (!isstr && !onlyquotes) { | ||
| // for numeric types make very strict check | ||
| if (std::isalnum(c) || std::strchr(".:+-", c)) | ||
| sanitized.Append(c); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not necessarily produce a number, so I'm not sure what's the goal of this sanitization (what if the string is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Goal to avoid symbols sequence which can be interpreted as C/C++ code invocation like
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're checking for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if every non-operator is a digit this will still cause |
||
| } else if ((c == '\"') || (c == '\\')) { | ||
| // escape quotes inside string | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite bizarre if I understand correctly: if an argument is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will be delivered to C++ as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we rather require all internal quotes to be already escaped in the argument? If the incoming argument is supposed to be a valid string literal it would not be valid if it contains unescaped quotes, which may indicate some other kind of error (on the user side or otherwise)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On each layer escape character can disappear.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That sounds like a bug from the previous layers...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are many differences how quotes escaped in URL and in C++. |
||
| sanitized.Append('\\'); | ||
| sanitized.Append(c); | ||
| } else if (!std::iscntrl(c)) { | ||
| // exclude special chars | ||
| sanitized.Append(c); | ||
| } | ||
| } | ||
| if (isstr) | ||
| sanitized.Append('\"'); // string end with quotes | ||
| return sanitized; | ||
| } | ||
|
|
||
|
|
||
| //////////////////////////////////////////////////////////////////////////////// | ||
| /// Execute command for specified object | ||
| /// | ||
|
|
@@ -638,6 +672,10 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & | |
| val = sval.Data(); | ||
| } | ||
|
|
||
| // only value provided from outside should be sanitized | ||
| // default argument value or internal pointer will not | ||
| Bool_t sanitize = kFALSE; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be useful to have a comment here explaining when/why we do not sanitize.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When processing POST data special value created directly in the code - including pointer casting.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! This as a code comment would be helpful. |
||
|
|
||
| if ((val != nullptr) && (strcmp(val, "_this_") == 0)) { | ||
| // special case - object itself is used as argument | ||
| sval.Form("(%s*)0x%zx", obj_cl->GetName(), (size_t)obj_ptr); | ||
|
|
@@ -667,7 +705,7 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & | |
| } | ||
| val = sval.Data(); | ||
| } else if ((strcmp(val, "_post_object_") == 0) && url.HasOption("_post_class_")) { | ||
| TString clname = url.GetValueFromOptions("_post_class_"); | ||
| TString clname = DecodeUrlOptionValue(url.GetValueFromOptions("_post_class_"), kTRUE); | ||
| TClass *arg_cl = gROOT->GetClass(clname, kTRUE, kTRUE); | ||
| if ((arg_cl != nullptr) && (arg_cl->GetBaseClassOffset(TObject::Class()) == 0) && (post_obj == nullptr)) { | ||
| post_obj = (TObject *)arg_cl->New(); | ||
|
|
@@ -692,7 +730,11 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & | |
| } else if (strcmp(val, "_post_length_") == 0) { | ||
| sval.Form("%ld", (long)fCurrentArg->GetPostDataLength()); | ||
| val = sval.Data(); | ||
| } else { | ||
| sanitize = kTRUE; | ||
| } | ||
| } else { | ||
| sanitize = val != nullptr; | ||
| } | ||
|
|
||
| if (!val) | ||
|
|
@@ -708,15 +750,11 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & | |
| if (call_args.Length() > 0) | ||
| call_args += ", "; | ||
|
|
||
| if ((strcmp(arg->GetFullTypeName(), "const char*") == 0) || (strcmp(arg->GetFullTypeName(), "Option_t*") == 0)) { | ||
| int len = strlen(val); | ||
| if ((strlen(val) < 2) || (*val != '\"') || (val[len - 1] != '\"')) | ||
| call_args.Append(TString::Format("\"%s\"", val)); | ||
| else | ||
| call_args.Append(val); | ||
| } else { | ||
| call_args.Append(val); | ||
| } | ||
| Bool_t isstr = (strcmp(arg->GetFullTypeName(), "const char*") == 0) || | ||
| (strcmp(arg->GetFullTypeName(), "Option_t*") == 0) || | ||
| (strcmp(arg->GetFullTypeName(), "string") == 0); | ||
|
|
||
| call_args.Append(SanitizeArgument(isstr, val, !sanitize)); | ||
| } | ||
|
|
||
| TMethodCall *call = nullptr; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How long can
valuebe? Since we're trying to sanitize the input, shouldn't we also put a reasonable max length?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no such limit and I cannot make here any assumption.
civetwebcuts URL length by 16K.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backend is not guaranteed to be civetweb in principle, is it?
We should put an artificial limit here so we don't have to worry about huge allocations/reallocations/copies/iterations and we could in principle even preallocate the output string once and do a single pass over the string to validate it.