Skip to content
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

Include Windows OS in Bootstrap initializeNatives() check for definitelyRunningAsRoot() #4656

Merged
merged 6 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Bumps `reactor-core` from 3.4.18 to 3.4.23 ([#4548](https://github.com/opensearch-project/OpenSearch/pull/4548))
- Bumps `jempbox` from 1.8.16 to 1.8.17 ([#4550](https://github.com/opensearch-project/OpenSearch/pull/4550))
- Bumps `hadoop-hdfs` from 3.3.3 to 3.3.4 ([#4644](https://github.com/opensearch-project/OpenSearch/pull/4644))
- Bumps `jna` from 5.11.0 to 5.12.1 ([#4656](https://github.com/opensearch-project/OpenSearch/pull/4656))

### Changed
- Dependency updates (httpcore, mockito, slf4j, httpasyncclient, commons-codec) ([#4308](https://github.com/opensearch-project/OpenSearch/pull/4308))
Expand All @@ -57,6 +58,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Further simplification of the ZIP publication implementation ([#4360](https://github.com/opensearch-project/OpenSearch/pull/4360))
- Relax visibility of the HTTP_CHANNEL_KEY and HTTP_SERVER_CHANNEL_KEY to make it possible for the plugins to access associated Netty4HttpChannel / Netty4HttpServerChannel instance ([#4638](https://github.com/opensearch-project/OpenSearch/pull/4638))
- Load the deprecated master role in a dedicated method instead of in setAdditionalRoles() ([#4582](https://github.com/opensearch-project/OpenSearch/pull/4582))
- Include Windows OS in Bootstrap initializeNatives() check for definitelyRunningAsRoot() ([#4656](https://github.com/opensearch-project/OpenSearch/pull/4656))
- Add APIs (GET/PUT) to decommission awareness attribute ([#4261](https://github.com/opensearch-project/OpenSearch/pull/4261))

### Deprecated
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ dependencies {
api 'com.netflix.nebula:gradle-info-plugin:11.3.3'
api 'org.apache.rat:apache-rat:0.13'
api 'commons-io:commons-io:2.7'
api "net.java.dev.jna:jna:5.11.0"
api "net.java.dev.jna:jna:5.12.1"
api 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
api 'org.jdom:jdom2:2.0.6.1'
api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10'
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/version.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ slf4j = 1.7.36
asm = 9.3

# when updating the JNA version, also update the version in buildSrc/build.gradle
jna = 5.5.0
jna = 5.12.1

netty = 4.1.79.Final
joda = 2.10.13
Expand Down
1 change: 1 addition & 0 deletions server/licenses/jna-5.12.1.jar.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b1e93a735caea94f503e95e6fe79bf9cdc1e985d
1 change: 0 additions & 1 deletion server/licenses/jna-5.5.0.jar.sha1

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.bootstrap;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.Structure;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.Constants;

import java.util.List;

/**
* Library for Windows/Advapi32
*
* @opensearch.internal
*/
final class JNAAdvapi32Library {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we using the JNA advapi32 wrapper?

Because we are only using the jna dependency, not jna-platform. See also the JNAKernel32Library and JNACLibrary classes which could be eliminated.

jna-platform is a 1.3MB JAR and also introduces a modular transitive dependency on java.desktop under JPMS. Happy to switch the code to use this added dependency, but given the very limited use of library mappings, I don't think it's needed (yet).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love copy-pasting code from 3P libraries to save 1.3MB because that's how we miss fixes in the library, but I don't have a strong opinion about this. I'll hit approve on this PR and we can fix this later. How do you feel about this issue @nknize?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love copy-pasting code from 3P libraries to save 1.3MB because that's how we miss fixes in the library

That's more likely with util-based functions, but these mappings are rather straightforward and I actually researched the original API reference to confirm correctness for these. I do admit to copying the javadocs (derived from Win32API) from from some guy named @dblock and I don't think he made a mistake. Your opinion of him may vary though. 😁

More seriously, though, there's a point where there are too many native mappings and we should switch to a dependency. I don't think we're there yet (three classes with a handful of functions in them) but we are probably at least halfway there.


private static final Logger logger = LogManager.getLogger(JNAAdvapi32Library.class);

private static final class Holder {
private static final JNAAdvapi32Library instance = new JNAAdvapi32Library();
}

private JNAAdvapi32Library() {
if (Constants.WINDOWS) {
try {
Native.register("advapi32");
logger.debug("windows/Advapi32 library loaded");
} catch (NoClassDefFoundError e) {
logger.warn("JNA not found. native methods and handlers will be disabled.");
} catch (UnsatisfiedLinkError e) {
logger.warn("unable to link Windows/Advapi32 library. native methods and handlers will be disabled.");
}
}
}

static JNAAdvapi32Library getInstance() {
return Holder.instance;
}

/**
* Access right required to query an access token.
* Used by {@link #OpenProcessToken(Pointer, int, PointerByReference)}.
*
* https://learn.microsoft.com/en-us/windows/win32/secauthz/access-rights-for-access-token-objects
*/
public static final int TOKEN_QUERY = 0x0008;

/**
* TOKEN_INFORMATION_CLASS enumeration value that specifies the type of information being assigned to or retrieved from an access token.
* Used by {@link #GetTokenInformation(Pointer, int, Structure, int, IntByReference)}.
*
* https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class
*/
public static final int TOKEN_ELEVATION = 0x14;

/**
* Native call to the Advapi32 API to open the access token associated with a process.
*
* @param processHandle Handle to the process whose access token is opened.
* The process must have the PROCESS_QUERY_INFORMATION access permission.
* @param desiredAccess Specifies an access mask that specifies the requested types of access to the access token.
* These requested access types are compared with the discretionary access control list (DACL) of the token to determine which accesses are granted or denied.
* @param tokenHandle Pointer to a handle that identifies the newly opened access token when the function returns.
* @return If the function succeeds, the return value is true.
* If the function fails, the return value is false.
* To get extended error information, call GetLastError.
*/
native boolean OpenProcessToken(Pointer processHandle, int desiredAccess, PointerByReference tokenHandle);

/**
* Retrieves a specified type of information about an access token.
* The calling process must have appropriate access rights to obtain the information.
*
* @param tokenHandle Handle to an access token from which information is retrieved.
* If TokenInformationClass specifies TokenSource, the handle must have TOKEN_QUERY_SOURCE access.
* For all other TokenInformationClass values, the handle must have TOKEN_QUERY access.
* @param tokenInformationClass Specifies a value from the TOKEN_INFORMATION_CLASS enumerated type to identify the type of information the function retrieves.
* @param tokenInformation Pointer to a buffer the function fills with the requested information.
* The structure put into this buffer depends upon the type of information specified by the TokenInformationClass parameter.
* @param tokenInformationLength Specifies the size, in bytes, of the buffer pointed to by the TokenInformation parameter.
* If TokenInformation is NULL, this parameter must be zero.
* @param returnLength Pointer to a variable that receives the number of bytes needed for the buffer pointed to by the TokenInformation parameter.
* If this value is larger than the value specified in the TokenInformationLength parameter, the function fails and stores no data in the buffer.
* @return If the function succeeds, the return value is true.
* If the function fails, the return value is zero.
* To get extended error information, call GetLastError.
*/
native boolean GetTokenInformation(
Pointer tokenHandle,
int tokenInformationClass,
Structure tokenInformation,
int tokenInformationLength,
IntByReference returnLength
);

/**
* The TOKEN_ELEVATION structure indicates whether a token has elevated privileges.
*
* https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_elevation
*/
public static class TokenElevation extends Structure {
/**
* A nonzero value if the token has elevated privileges; otherwise, a zero value.
*/
public int TokenIsElevated;

@Override
protected List<String> getFieldOrder() {
return List.of("TokenIsElevated");
}
}
}
44 changes: 40 additions & 4 deletions server/src/main/java/org/opensearch/bootstrap/JNANatives.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,19 @@
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.Constants;
import org.opensearch.bootstrap.JNAAdvapi32Library.TokenElevation;
import org.opensearch.monitor.jvm.JvmInfo;

import java.nio.file.Path;

import static org.opensearch.bootstrap.JNAAdvapi32Library.TOKEN_ELEVATION;
import static org.opensearch.bootstrap.JNAAdvapi32Library.TOKEN_QUERY;
import static org.opensearch.bootstrap.JNAKernel32Library.SizeT;

/**
Expand Down Expand Up @@ -185,13 +190,44 @@ static String rlimitToString(long value) {

/** Returns true if user is root, false if not, or if we don't know */
static boolean definitelyRunningAsRoot() {
if (Constants.WINDOWS) {
return false; // don't know
}
try {
if (Constants.WINDOWS) {
JNAKernel32Library kernel32 = JNAKernel32Library.getInstance();
JNAAdvapi32Library advapi32 = JNAAdvapi32Library.getInstance();

// Fetch a pseudo handle for the current process.
// The pseudo handle need not be closed when it is no longer needed (calling CloseHandle is a no-op).
Pointer process = kernel32.GetCurrentProcess();
PointerByReference hToken = new PointerByReference();
// Fetch the process token for the current process, for which we know we have the access rights
if (!advapi32.OpenProcessToken(process, TOKEN_QUERY, hToken)) {
logger.warn(
"Unable to open the Process Token for the current process [" + JNACLibrary.strerror(Native.getLastError()) + "]"
);
return false;
}
// We have successfully opened the token. Ensure it gets closed after we use it.
try {
TokenElevation elevation = new TokenElevation();
IntByReference returnLength = new IntByReference();
if (!advapi32.GetTokenInformation(hToken.getValue(), TOKEN_ELEVATION, elevation, elevation.size(), returnLength)) {
logger.warn(
"Unable to get TokenElevation information for the current process ["
+ JNACLibrary.strerror(Native.getLastError())
+ "]"
);
return false;
}
// Nonzero value means elevated privileges
return elevation.TokenIsElevated > 0;
} finally {
kernel32.CloseHandle(hToken.getValue());
}
}
// For unix-based systems, check effective user ID of process
return JNACLibrary.geteuid() == 0;
} catch (UnsatisfiedLinkError e) {
// this will have already been logged by Kernel32Library, no need to repeat it
// this will have already been logged by Native Library, no need to repeat it
return false;
}
}
Expand Down