| // Copyright 2020 The Chromium OS Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "foomatic_shell/verifier.h" | 
 |  | 
 | #include <set> | 
 |  | 
 | #include <base/logging.h> | 
 | #include <base/no_destructor.h> | 
 |  | 
 | namespace foomatic_shell { | 
 |  | 
 | namespace { | 
 |  | 
 | // A set of allowed environment variables that may be set for executed commands. | 
 | const std::set<std::string> AllowedVariables() { | 
 |   static const base::NoDestructor<std::set<std::string>> variables({"NOPDF"}); | 
 |   return *variables; | 
 | } | 
 |  | 
 | bool HasPrefix(const std::string& str, const std::string& prefix) { | 
 |   if (prefix.size() > str.size()) | 
 |     return false; | 
 |   return (str.compare(0, prefix.size(), prefix) == 0); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | bool Verifier::VerifyScript(Script* script, int recursion_level) { | 
 |   DCHECK(script != nullptr); | 
 |   if (recursion_level > 5) { | 
 |     message_ = "too many recursive subshell invocations"; | 
 |     return false; | 
 |   } | 
 |  | 
 |   for (auto& pipeline : script->pipelines) { | 
 |     for (auto& segment : pipeline.segments) { | 
 |       // Save the position of the current segment (in case of an error). | 
 |       position_ = Position(segment); | 
 |       // Verify the segment. | 
 |       bool result = false; | 
 |       if (segment.command) { | 
 |         // It is a Command. | 
 |         result = VerifyCommand(segment.command.get()); | 
 |       } else { | 
 |         // It is a Script. | 
 |         DCHECK(segment.script); | 
 |         result = VerifyScript(segment.script.get(), recursion_level + 1); | 
 |       } | 
 |       if (!result) | 
 |         return false; | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool Verifier::VerifyCommand(Command* command) { | 
 |   DCHECK(command != nullptr); | 
 |  | 
 |   // Verify variables set for this command. | 
 |   for (auto& var : command->variables_with_values) { | 
 |     if (AllowedVariables().count(var.variable.value) == 0) { | 
 |       message_ = "variable " + var.variable.value + " is not allowed"; | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   const std::string& cmd = command->application.value; | 
 |  | 
 |   // The "cat" command is allowed <=> it has no parameters or it has only a | 
 |   // single parameter "-". | 
 |   if (cmd == "cat") { | 
 |     if (command->parameters.empty()) | 
 |       return true; | 
 |     if (command->parameters.size() == 1 && | 
 |         Value(command->parameters.front()) == "-") | 
 |       return true; | 
 |     message_ = "cat: disallowed parameter"; | 
 |     return false; | 
 |   } | 
 |  | 
 |   // The "cut" command is always allowed. | 
 |   if (cmd == "cut") | 
 |     return true; | 
 |  | 
 |   // The "date" command is allowed <=> it has no parameters with prefixes "-s" | 
 |   // or "--set". | 
 |   if (cmd == "date") { | 
 |     for (auto& parameter : command->parameters) { | 
 |       const std::string param = Value(parameter); | 
 |       if (HasPrefix(param, "-s") || HasPrefix(param, "--set")) { | 
 |         message_ = "date: disallowed parameter"; | 
 |         return false; | 
 |       } | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // The "echo" command is always allowed. | 
 |   if (cmd == "echo") | 
 |     return true; | 
 |  | 
 |   // The "gs" command is verified in separate method. | 
 |   if (cmd == "gs") | 
 |     return VerifyGs(command->parameters); | 
 |  | 
 |   // The "pdftops" command used by foomatic-rip is located at | 
 |   // /usr/libexec/cups/filter/pdftops, not /usr/bin/pdftops (a default one). | 
 |   // It takes 5 or 6 parameters. | 
 |   if (cmd == "pdftops") | 
 |     return true; | 
 |  | 
 |   // The "printf" command is always allowed. | 
 |   if (cmd == "printf") | 
 |     return true; | 
 |  | 
 |   // The "sed" command is allowed <=> it has no parameters with prefixes "-i" | 
 |   // or "--in-place". Moreover, the "--sandbox" parameter is added if not | 
 |   // already present. | 
 |   if (cmd == "sed") { | 
 |     bool sandbox = false; | 
 |     for (auto& parameter : command->parameters) { | 
 |       const std::string param = Value(parameter); | 
 |       if (param == "--sandbox") { | 
 |         sandbox = true; | 
 |         continue; | 
 |       } | 
 |       if (HasPrefix(param, "-i") || HasPrefix(param, "--in-place")) { | 
 |         message_ = "sed: disallowed parameter"; | 
 |         return false; | 
 |       } | 
 |     } | 
 |     if (!sandbox) { | 
 |       Token token; | 
 |       token.type = Token::Type::kNativeString; | 
 |       token.value = "--sandbox"; | 
 |       token.begin = token.end = command->application.end; | 
 |       const StringAtom string_atom = {{token}}; | 
 |       command->parameters.push_back(string_atom); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // All other commands are disallowed. | 
 |   message_ = "disallowed command: " + command->application.value; | 
 |   return false; | 
 | } | 
 |  | 
 | // Parameters “-dSAFER” and “-sOutputFile=-” must be present. | 
 | // No other “-sOutputFile=” parameters are allowed. | 
 | // Parameters “-dNOSAFER” and “-dALLOWPSTRANSPARENCY” are disallowed. | 
 | bool Verifier::VerifyGs(const std::vector<StringAtom>& parameters) { | 
 |   bool safer = false; | 
 |   bool output_file = false; | 
 |   for (auto& parameter : parameters) { | 
 |     const std::string param = Value(parameter); | 
 |     if (param == "-dPARANOIDSAFER" || param == "-dSAFER") { | 
 |       safer = true; | 
 |       continue; | 
 |     } | 
 |     if (param == "-sOutputFile=-") { | 
 |       output_file = true; | 
 |       continue; | 
 |     } | 
 |     if (HasPrefix(param, "-sOutputFile=") || param == "-dNOSAFER" || | 
 |         param == "-dALLOWPSTRANSPARENCY") { | 
 |       message_ = "gs: disallowed parameter"; | 
 |       return false; | 
 |     } | 
 |   } | 
 |   if (!safer) { | 
 |     message_ = "gs: the parameter -dSAFER is missing"; | 
 |     return false; | 
 |   } | 
 |   if (!output_file) { | 
 |     message_ = "gs: the parameter -sOutputFile=- is missing"; | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace foomatic_shell |