Why ptrace doesn't attach to process after setuid? - ptrace

I have a problem with my Linux daemon program. It starts with root privileges, does some configuration, then permanently drops privileges by switching to some user and group and continues working. The switch to the non-privileged user is done like this:
void switch_to_user_group(std::string const& username, std::string const& groupname)
{
// Switch to user/group
gid_t gid = getgid();
if (!groupname.empty())
{
gid = get_group_id(groupname);
if (0 != setgid(gid))
{
std::cout << "Failed to switch to group " << gid << std::endl;
std::abort();
}
}
if (!username.empty())
{
uid_t uid = get_user_id(username);
if (initgroups(username.c_str(), gid) != 0)
{
std::cout << "initgroups failed" << std::endl;
std::abort();
}
if (0 != setuid(uid))
{
std::cout << "Failed to switch to user " << uid << std::endl;
std::abort();
}
}
}
The switch performs correctly, I can see the process in ps and top running under my user. The problem is that I can't attach to this process from gdb, even after it has dropped the privileges. The output is:
Attaching to process 15716
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
I'm running gdb under the same user the process switched to, and I am able to attach to other processes that were initially started under under that user. I tried this on Kubuntu 13.10 (YAMA is disabled), Debian 6 and 7 with the same result.
So my questions are:
Why can't ptrace attach to a process that has the same effective and real UID as gdb?
Is it possible to drop privileges of my program in a way so that I can attach to it from the unprivileged gdb? How?
Thanks.

I found the solution on my own.
There is a 'dumpable' flag in the kernel for every process. When the process performs setuid or setgid (at least, in my case, when the process drops privileges) this flag gets cleared and normal users can't attach to this process with a debugger, and the process crashes also do not produce a crash dump. This is done for security reasons to protect any sensitive data obtained with elevated privileges that may be in the process memory.
To solve the problem the process can explicitly allow debugging by setting the 'dumpable' flag to 1.
prctl(PR_SET_DUMPABLE, 1);
This has to be done after the setgid/setuid calls.

Related

How can I redirect the standard output from cmd.exe

This has proven difficult to search as most results are all about redirecting from WITHIN cmd.exe rather than the output of cmd.exe itself.
I have a simple C# example showing a working and non-working test of redirecting process output and just printing the outputted values.
void Main()
{
// Calling nslookup directly works as expected
ProcessStartInfo joy = new ProcessStartInfo("nslookup", #"google.com 8.8.8.8");
// Calling nslookup as a command to cmd.exe does not work as expected
ProcessStartInfo noJoy = new ProcessStartInfo(Environment.ExpandEnvironmentVariables("%COMSPEC%"), #"/C nslookup google.com 8.8.8.8");
Console.WriteLine($"*** Running \"{joy.FileName} {joy.Arguments}\"...");
Console.WriteLine();
Run(joy);
Console.WriteLine();
Console.WriteLine($"*** Running \"{noJoy.FileName} {noJoy.Arguments}\"...");
Console.WriteLine();
Run(noJoy);
}
void Run(ProcessStartInfo startInfo)
{
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
Process proc = new Process();
proc.StartInfo = startInfo;
proc.EnableRaisingEvents = true;
proc.Exited += ReceiveExitNotification;
proc.ErrorDataReceived += ReceiveStandardErrorData;
proc.OutputDataReceived += ReceiveStandardOutputData;
proc.Start();
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
proc.ExitCode.Dump();
}
void ReceiveStandardOutputData(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
void ReceiveStandardErrorData(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
void ReceiveExitNotification(object sender, EventArgs e)
{
Console.WriteLine("Exited");
}
And here's the output I'm getting from the above
*** Running "nslookup google.com 8.8.8.8"...
Non-authoritative answer:
Server: dns.google
Address: 8.8.8.8
Name: google.com
Addresses: 2607:f8b0:4002:c08::8b
2607:f8b0:4002:c08::64
2607:f8b0:4002:c08::65
2607:f8b0:4002:c08::66
172.217.10.206
null
null
Exited
0
*** Running "C:\windows\system32\cmd.exe /C nslookup google.com 8.8.8.8"...
null
null
Exited
0
The choice of nslookup in the example is arbitrary, I've tried many others including commands with side effects so I can be sure it's being executed as expected.
I have tried with synchronous reads but no change.
I have no reason to believe it's C# or .NET related. I may try a direct CreateProcess() test to confirm.
For context, it's a batch file from which I'm actually looking to get the output, that's why the intermediate cmd.exe process is needed.
Further context, it's actually an MSBuild Exec task from which I'm trying to get the output, so I have limited control of the actual invocation, but I've watched the task run in the debugger and narrowed it down to this issue.
TLDR; The code example in the question works just fine on any normal machine.
So as it turns out this is a permissions issue. This is a company computer and I have restricted rights, however they have software installed that gives administrative rights to particular processes. cmd.exe is one of those processes, so by default it launches as admin and so I cannot read the output stream from my non-elevated process.
Some ideas that almost work around the issue:
From a cmd.exe prompt I can run set __COMPAT_LAYER=RUNASINVOKER then run a second cmd.exe which runs unelevated, but this doesn't really help as I still can't get that stream. Setting the __COMPAT_LAYER environment variable seems to only affect processes launched from cmd.exe (from not CreateProcess() which .NET's Process.Start() uses).
RunAs.exe has a /trustlevel switch with which I can run an unelevated command, but then my Process object is for runas which does not handle any redirection or even stay open for the life of the child process, so still no good.
But in my case I think the simplest solution is best. Copy cmd.exe to another directory and add that to the top of the path. This fixes the elevation issue and even works as a final solution to my actual problem by working event with my limited access to the invocation call through the MSBuild task.

Setting NVRAM variable using WinAPI

I'm trying to setup my own NVRAM variable (nv+rt+bs) using WinAPI (SetFirmwareEnvironmentVariable). It fails with my variable and successfully completed with BootOrder. What do I do wrong? Is it possible to setup my own variable using WinAPI?
I'm trying to setup my variable using this code
void setMyNVRAM()
{
// here I setup SE_SYSTEM_ENVIRONMENT_NAME privilege
uint16_t value = 0x5aa5;
if (!SetFirmwareEnvironmentVariable(L"MY_VARIABLE",
L"{12345678-1234-1234-1234-123456789012}", &value, sizeof (value)))
{
std::cout << "error while settin up MY_VARIABLE. Error code: "
<< GetLastError() << std::endl;
}
else
{
std::cout << "success" << std::endl;
}
}
SetFirmwareEnvironmentVariable returns 0 and GetLastError() returns 5 (access denied). I also tried to write into BootOrder variable and it was successfully completed.
According to the instructions on the MSDN.
To write a firmware environment variable, the user account that the
app is running under must have the SE_SYSTEM_ENVIRONMENT_NAME
privilege. A Universal Windows app must be run from an administrator
account and follow the requirements outlined in Access UEFI firmware
variables from a Universal Windows App.
Customizing user privileges for an account in Windows
A similar discussion: How i can set firmware environment variable in UEFI driver

Why nfsd clears S_ISUID bit in SETATTR if I try to set S_ISUID and owner at the same time?

NFSv3 protocol specifies that SETATTR can set file/dir mode and owner at the same time (as well as few other things). And yet Linux implementation of nfsd behaves in a quite strange way:
/* Revoke setuid/setgid on chown */
if (!S_ISDIR(inode->i_mode) &&
((iap->ia_valid & ATTR_UID) || (iap->ia_valid & ATTR_GID))) {
iap->ia_valid |= ATTR_KILL_PRIV;
if (iap->ia_valid & ATTR_MODE) {
/* we're setting mode too, just clear the s*id bits */
iap->ia_mode &= ~S_ISUID;
if (iap->ia_mode & S_IXGRP)
iap->ia_mode &= ~S_ISGID;
} else {
/* set ATTR_KILL_* bits and let VFS handle it */
iap->ia_valid |= (ATTR_KILL_SUID | ATTR_KILL_SGID);
}
}
What is the reason for this? I spent a lot of time tracking it down in some old app (that for some reason could not copy a file via NFS without losing suid bit). Is it a bug? It happens even if new owner is the same.
Disabling SUID is some sort of protection against untrusted NFS servers: If administrator of your local machine doesn't trust the NFS server, it cannot afford to execute files from the server with root privileges. See also article NFS, no_root_squash and SUID - Basic NFS Security.
It is a bug in Linux implementation of NFS server. See NFS specs.

Where does tmux keep logs on OS X

tmux has been crashing a lot lately, and I'm not sure why. I want to look into it further, but I don't know where I can find any kind of logs, or error messages. So far, my googling for "tmux log location" and the like has come up empty.
I'm running OS X, and installed tmux via Homebrew.
The manual page needs some work (you may not see the feature at first). But starting from the source code (referring to version 2.1 in tty.c) you may see
if (debug_level > 3) {
xsnprintf(out, sizeof out, "tmux-out-%ld.log", (long) getpid());
fd = open(out, O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd != -1 && fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
fatal("fcntl failed");
tty->log_fd = fd;
}
The -v flag sets the debug_level value; repeating it increases the value. Back to the manual page:
-v
Request verbose logging. This option may be specified multiple times for increasing verbosity. Log messages will be saved into tmux-client-PID.log and tmux-server-PID.log files in the current directory, where PID is the PID of the server or client process.

Are Windows Services Restricted from Reading from %WINDIR% (C:\Windows)?

I've got an application running as a Windows service that wants to read a file specified by a relative path. Since the service is running under C:\Windows\system32 (on Server 2003 and Windows 7), I figure it should be reading the file from there. However, the file read always fails.
I put together some simple test code to try to open a file for reading, using an absolute path. While the service succeeds for files such as C:\Temp\foo.txt, it always fails for files like C:\Windows\foo.txt and C:\Windows\system32\foo.txt . GetLastError() returns 2.
Am I running into an access issue? I couldn't find authoritative documentation on this. Is there any workaround?
Update:
The file test code is generic and straightforward:
std::ofstream out;
//...
std::string fileName("C:\\Windows\\system32\\Foo.txt");
hFile = CreateFile(fileName.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
out << "Could not create file handle! (" << GetLastError() << ")" << std::endl;
}
else {
out << "Successfully opened file!" << std::endl;
CloseHandle(hFile);
}
Try running the windows service from Local System account. By default the service may be running from "Network Service" account.
To change the settings, Open Windows Service Manager (Run-> services.msc) and double-click your service. On property window select 2nd Tab "Log On" and change it to run with Local System account.
Error code 2 is ERROR_FILE_NOT_FOUND so it's likelier that the path you give simply does not exist or the file does not exist in that path. Without the relevant flags from CreateFile it's hard to give you a better answer.
But generally - under default conditions - a service would be allowed to read in that folder.
One more thing came to mind. How do you obtain the path (C:\Windows in your case)? The proper means are to use the API (e.g. GetWindowsDirectory) for this and not hardcode it.

Resources