// Copyright 2014 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <unistd.h>

#include <ostream>

#include "base/check_op.h"
#include "build/build_config.h"

namespace crashpad {

void DropPrivileges() {
  gid_t gid = getgid();
  uid_t uid = getuid();

#if BUILDFLAG(IS_APPLE)
  // Based on the POSIX.1-2008 2013 edition documentation for setreuid() and
  // setregid(), setreuid() and setregid() alone should be sufficient to drop
  // privileges. The standard specifies that the saved ID should be set to the
  // effective ID whenever the real ID is not -1, or whenever the effective ID
  // is set not equal to the real ID. This code never specifies -1, so the
  // setreuid() and setregid() alone should work according to the standard.
  //
  // In practice, on older versions of macOS, setuid() and setgid() (or
  // seteuid() and setegid()) must be called first. Otherwise, setreuid() and
  // setregid() do not alter the saved IDs, leaving open the possibility for
  // future privilege escalation.
  //
  // The problem exists in 10.9.5 xnu-2422.115.4/bsd/kern/kern_prot.c
  // setreuid(). Based on its comments, it purports to set the svuid to the new
  // euid when the old svuid doesn’t match one of the new ruid and euid. This
  // isn’t how POSIX.1-2008 says it should behave, but it should work for this
  // function’s purposes. In reality, setreuid() doesn’t even do this: it sets
  // the svuid to the old euid, which does not drop privileges when the old euid
  // is different from the desired euid. The workaround of calling setuid() or
  // seteuid() before setreuid() works because it sets the euid so that by the
  // time setreuid() runs, the old euid is actually the value that ought to be
  // set as the svuid. setregid() is similar. This bug was reported as radar
  // 18987552, fixed in 10.10.3 and security updates to 10.9.5 and 10.8.5.
  //
  // setuid() and setgid() alone will only set the saved IDs when running as
  // root. When running a setuid non-root or setgid program, they do not alter
  // the saved ID, and do not effect a permanent privilege drop.
  gid_t egid = getegid();
  PCHECK(setgid(gid) == 0) << "setgid";
  PCHECK(setregid(gid, gid) == 0) << "setregid";

  uid_t euid = geteuid();
  PCHECK(setuid(uid) == 0) << "setuid";
  PCHECK(setreuid(uid, uid) == 0) << "setreuid";

  if (uid != 0) {
    // Because the setXid()+setreXid() interface to change IDs is fragile,
    // ensure that privileges cannot be regained. This can only be done if the
    // real user ID (and now the effective user ID as well) is not root, because
    // root always has permission to change identity.
    if (euid != uid) {
      CHECK_EQ(seteuid(euid), -1);
    }
    if (egid != gid) {
      CHECK_EQ(setegid(egid), -1);
    }
  }
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  PCHECK(setresgid(gid, gid, gid) == 0) << "setresgid";
  PCHECK(setresuid(uid, uid, uid) == 0) << "setresuid";

  // Don’t check to see if privileges can be regained on Linux, because on
  // Linux, it’s not as simple as ensuring that this can’t be done if non-root.
  // Instead, the ability to change user and group IDs are controlled by the
  // CAP_SETUID and CAP_SETGID capabilities, which may be granted to non-root
  // processes. Since the setresXid() interface is well-defined, it shouldn’t be
  // necessary to perform any additional checking anyway.
  //
  // TODO(mark): Drop CAP_SETUID and CAP_SETGID if present and non-root?
#else
#error Port this function to your system.
#endif
}

}  // namespace crashpad