mirror of
https://github.com/openjdk/jdk.git
synced 2026-08-04 15:15:23 +00:00
8380391: Update java.smartcardio finalizer to use Cleaner
Reviewed-by: vklang, valeriep, myankelevich
This commit is contained in:
parent
218fb7f494
commit
1b79e91f2c
@ -232,6 +232,7 @@ module java.base {
|
||||
exports jdk.internal.ref to
|
||||
java.desktop,
|
||||
java.net.http,
|
||||
java.smartcardio,
|
||||
jdk.naming.dns;
|
||||
exports jdk.internal.reflect to
|
||||
java.logging,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -81,12 +81,15 @@
|
||||
* CardTerminal terminal = terminals.get(0);
|
||||
* // establish a connection with the card
|
||||
* Card card = terminal.connect("T=0");
|
||||
* System.out.println("card: " + card);
|
||||
* CardChannel channel = card.getBasicChannel();
|
||||
* ResponseAPDU r = channel.transmit(new CommandAPDU(c1));
|
||||
* System.out.println("response: " + toString(r.getBytes()));
|
||||
* // disconnect
|
||||
* card.disconnect(false);
|
||||
* try {
|
||||
* System.out.println("card: " + card);
|
||||
* CardChannel channel = card.getBasicChannel();
|
||||
* ResponseAPDU r = channel.transmit(new CommandAPDU(c1));
|
||||
* System.out.println("response: " + toString(r.getBytes()));
|
||||
* } finally {
|
||||
* // disconnect
|
||||
* card.disconnect(false);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @since 1.6
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -26,6 +26,9 @@
|
||||
package sun.security.smartcardio;
|
||||
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.internal.ref.CleanerFactory;
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.lang.ref.Reference;
|
||||
|
||||
import javax.smartcardio.*;
|
||||
import static sun.security.smartcardio.PCSC.*;
|
||||
@ -43,9 +46,6 @@ final class CardImpl extends Card {
|
||||
// the terminal that created this card
|
||||
private final TerminalImpl terminal;
|
||||
|
||||
// the native SCARDHANDLE
|
||||
final long cardId;
|
||||
|
||||
// atr of this card
|
||||
private final ATR atr;
|
||||
|
||||
@ -55,12 +55,45 @@ final class CardImpl extends Card {
|
||||
// the basic logical channel (channel 0)
|
||||
private final ChannelImpl basicChannel;
|
||||
|
||||
// state of this card connection
|
||||
private volatile State state;
|
||||
|
||||
// thread holding exclusive access to the card, or null
|
||||
private volatile Thread exclusiveThread;
|
||||
|
||||
/* State and code for cleanup */
|
||||
static final class Context implements Runnable {
|
||||
// the native SCARDHANDLE
|
||||
final long cardId;
|
||||
|
||||
// state of this card connection
|
||||
private volatile State state;
|
||||
|
||||
private Context(long cardId, State state) {
|
||||
this.cardId = cardId;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cleaning action calls SCardDisconnect if the card state is OK,
|
||||
* otherwise it does nothing.
|
||||
*/
|
||||
public void run() {
|
||||
if (state == State.OK) {
|
||||
state = State.DISCONNECTED;
|
||||
try {
|
||||
SCardDisconnect(cardId, SCARD_LEAVE_CARD);
|
||||
} catch (PCSCException e) {
|
||||
// This will be swallowed if thrown when run by the Cleaner
|
||||
// thread, and never thrown if called via Cleanable.clean()
|
||||
// (only called if state != OK.)
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Context context;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
|
||||
CardImpl(TerminalImpl terminal, String protocol) throws PCSCException {
|
||||
this.terminal = terminal;
|
||||
int sharingMode = SCARD_SHARE_SHARED;
|
||||
@ -83,36 +116,50 @@ final class CardImpl extends Card {
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported protocol " + protocol);
|
||||
}
|
||||
cardId = SCardConnect(terminal.contextId, terminal.name,
|
||||
long localCardId = SCardConnect(terminal.contextId, terminal.name,
|
||||
sharingMode, connectProtocol);
|
||||
|
||||
this.context = new Context(localCardId, State.OK);
|
||||
this.cleanable = CleanerFactory.cleaner().register(this, this.context);
|
||||
|
||||
byte[] status = new byte[2];
|
||||
byte[] atrBytes = SCardStatus(cardId, status);
|
||||
byte[] atrBytes = SCardStatus(localCardId, status);
|
||||
atr = new ATR(atrBytes);
|
||||
this.protocol = status[1] & 0xff;
|
||||
basicChannel = new ChannelImpl(this, 0);
|
||||
state = State.OK;
|
||||
}
|
||||
|
||||
void checkState() {
|
||||
State s = state;
|
||||
if (s == State.DISCONNECTED) {
|
||||
throw new IllegalStateException("Card has been disconnected");
|
||||
} else if (s == State.REMOVED) {
|
||||
throw new IllegalStateException("Card has been removed");
|
||||
try {
|
||||
State s = context.state;
|
||||
if (s == State.DISCONNECTED) {
|
||||
throw new IllegalStateException("Card has been disconnected");
|
||||
} else if (s == State.REMOVED) {
|
||||
throw new IllegalStateException("Card has been removed");
|
||||
}
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
if (state != State.OK) {
|
||||
return false;
|
||||
}
|
||||
// ping card via SCardStatus
|
||||
try {
|
||||
SCardStatus(cardId, new byte[2]);
|
||||
return true;
|
||||
} catch (PCSCException e) {
|
||||
state = State.REMOVED;
|
||||
return false;
|
||||
if (context.state != State.OK) {
|
||||
return false;
|
||||
}
|
||||
// ping card via SCardStatus
|
||||
try {
|
||||
SCardStatus(context.cardId, new byte[2]);
|
||||
return true;
|
||||
} catch (PCSCException e) {
|
||||
context.state = State.REMOVED;
|
||||
// state has been set != OK. The cleaning action is now a noop.
|
||||
// Deregister from Cleaner to reduce reference tracking.
|
||||
cleanable.clean();
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,8 +172,15 @@ final class CardImpl extends Card {
|
||||
}
|
||||
|
||||
void handleError(PCSCException e) {
|
||||
if (e.code == SCARD_W_REMOVED_CARD) {
|
||||
state = State.REMOVED;
|
||||
try {
|
||||
if (e.code == SCARD_W_REMOVED_CARD) {
|
||||
context.state = State.REMOVED;
|
||||
// state has been set != OK. The cleaning action is now a noop.
|
||||
// Deregister from Cleaner to reduce reference tracking.
|
||||
cleanable.clean();
|
||||
}
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,21 +218,25 @@ final class CardImpl extends Card {
|
||||
private static byte[] commandOpenChannel = new byte[] {0, 0x70, 0, 0, 1};
|
||||
|
||||
public CardChannel openLogicalChannel() throws CardException {
|
||||
checkSecurity("openLogicalChannel");
|
||||
checkState();
|
||||
checkExclusive();
|
||||
try {
|
||||
byte[] response = SCardTransmit
|
||||
(cardId, protocol, commandOpenChannel, 0, commandOpenChannel.length);
|
||||
if ((response.length != 3) || (getSW(response) != 0x9000)) {
|
||||
throw new CardException
|
||||
("openLogicalChannel() failed, card response: "
|
||||
+ PCSC.toString(response));
|
||||
checkSecurity("openLogicalChannel");
|
||||
checkState();
|
||||
checkExclusive();
|
||||
try {
|
||||
byte[] response = SCardTransmit
|
||||
(context.cardId, protocol, commandOpenChannel, 0, commandOpenChannel.length);
|
||||
if ((response.length != 3) || (getSW(response) != 0x9000)) {
|
||||
throw new CardException
|
||||
("openLogicalChannel() failed, card response: "
|
||||
+ PCSC.toString(response));
|
||||
}
|
||||
return new ChannelImpl(this, response[0]);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("openLogicalChannel() failed", e);
|
||||
}
|
||||
return new ChannelImpl(this, response[0]);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("openLogicalChannel() failed", e);
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,87 +251,98 @@ final class CardImpl extends Card {
|
||||
}
|
||||
|
||||
public synchronized void beginExclusive() throws CardException {
|
||||
checkSecurity("exclusive");
|
||||
checkState();
|
||||
if (exclusiveThread != null) {
|
||||
throw new CardException
|
||||
("Exclusive access has already been assigned to Thread "
|
||||
+ exclusiveThread.getName());
|
||||
}
|
||||
try {
|
||||
SCardBeginTransaction(cardId);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("beginExclusive() failed", e);
|
||||
checkSecurity("exclusive");
|
||||
checkState();
|
||||
if (exclusiveThread != null) {
|
||||
throw new CardException
|
||||
("Exclusive access has already been assigned to Thread "
|
||||
+ exclusiveThread.getName());
|
||||
}
|
||||
try {
|
||||
SCardBeginTransaction(context.cardId);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("beginExclusive() failed", e);
|
||||
}
|
||||
exclusiveThread = Thread.currentThread();
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
exclusiveThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
public synchronized void endExclusive() throws CardException {
|
||||
checkState();
|
||||
if (exclusiveThread != Thread.currentThread()) {
|
||||
throw new IllegalStateException
|
||||
("Exclusive access not assigned to current Thread");
|
||||
}
|
||||
try {
|
||||
SCardEndTransaction(cardId, SCARD_LEAVE_CARD);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("endExclusive() failed", e);
|
||||
checkState();
|
||||
if (exclusiveThread != Thread.currentThread()) {
|
||||
throw new IllegalStateException
|
||||
("Exclusive access not assigned to current Thread");
|
||||
}
|
||||
try {
|
||||
SCardEndTransaction(context.cardId, SCARD_LEAVE_CARD);
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("endExclusive() failed", e);
|
||||
} finally {
|
||||
exclusiveThread = null;
|
||||
}
|
||||
} finally {
|
||||
exclusiveThread = null;
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] transmitControlCommand(int controlCode, byte[] command)
|
||||
throws CardException {
|
||||
checkSecurity("transmitControl");
|
||||
checkState();
|
||||
checkExclusive();
|
||||
if (command == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
try {
|
||||
byte[] r = SCardControl(cardId, controlCode, command);
|
||||
return r;
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("transmitControlCommand() failed", e);
|
||||
checkSecurity("transmitControl");
|
||||
checkState();
|
||||
checkExclusive();
|
||||
if (command == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
try {
|
||||
byte[] r = SCardControl(context.cardId, controlCode, command);
|
||||
return r;
|
||||
} catch (PCSCException e) {
|
||||
handleError(e);
|
||||
throw new CardException("transmitControlCommand() failed", e);
|
||||
}
|
||||
} finally {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(boolean reset) throws CardException {
|
||||
if (reset) {
|
||||
checkSecurity("reset");
|
||||
}
|
||||
if (state != State.OK) {
|
||||
return;
|
||||
}
|
||||
checkExclusive();
|
||||
try {
|
||||
SCardDisconnect(cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD));
|
||||
} catch (PCSCException e) {
|
||||
throw new CardException("disconnect() failed", e);
|
||||
if (reset) {
|
||||
checkSecurity("reset");
|
||||
}
|
||||
if (context.state != State.OK) {
|
||||
return;
|
||||
}
|
||||
checkExclusive();
|
||||
try {
|
||||
SCardDisconnect(context.cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD));
|
||||
} catch (PCSCException e) {
|
||||
throw new CardException("disconnect() failed", e);
|
||||
} finally {
|
||||
context.state = State.DISCONNECTED;
|
||||
exclusiveThread = null;
|
||||
// state has been set != OK. The cleaning action is now a noop.
|
||||
// Deregister from Cleaner to reduce reference tracking.
|
||||
cleanable.clean();
|
||||
}
|
||||
} finally {
|
||||
state = State.DISCONNECTED;
|
||||
exclusiveThread = null;
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "PC/SC card in " + terminal.name
|
||||
+ ", protocol " + getProtocol() + ", state " + state;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (state == State.OK) {
|
||||
state = State.DISCONNECTED;
|
||||
SCardDisconnect(cardId, SCARD_LEAVE_CARD);
|
||||
}
|
||||
return "PC/SC card in " + terminal.name
|
||||
+ ", protocol " + getProtocol() + ", state " + context.state;
|
||||
} finally {
|
||||
super.finalize();
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -189,7 +189,7 @@ final class ChannelImpl extends CardChannel {
|
||||
" exceeded maximum " + RESPONSE_ITERATIONS);
|
||||
}
|
||||
byte[] response = SCardTransmit
|
||||
(card.cardId, card.protocol, command, 0, n);
|
||||
(card.context.cardId, card.protocol, command, 0, n);
|
||||
int rn = response.length;
|
||||
if (getresponse && (rn >= 2) && (n >= 1)) {
|
||||
// see ISO 7816/2005, 5.1.3
|
||||
@ -280,7 +280,7 @@ final class ChannelImpl extends CardChannel {
|
||||
byte[] com = new byte[] {0x00, 0x70, (byte)0x80, 0};
|
||||
com[3] = (byte)getChannelNumber();
|
||||
setChannel(com);
|
||||
byte[] res = SCardTransmit(card.cardId, card.protocol, com, 0, com.length);
|
||||
byte[] res = SCardTransmit(card.context.cardId, card.protocol, com, 0, com.length);
|
||||
if (isOK(res) == false) {
|
||||
throw new CardException("close() failed: " + PCSC.toString(res));
|
||||
}
|
||||
|
||||
@ -666,6 +666,7 @@ jdk_security_manual_no_input = \
|
||||
com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java \
|
||||
com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java \
|
||||
sun/security/smartcardio/TestChannel.java \
|
||||
sun/security/smartcardio/TestCleaner.java \
|
||||
sun/security/smartcardio/TestConnect.java \
|
||||
sun/security/smartcardio/TestConnectAgain.java \
|
||||
sun/security/smartcardio/TestControl.java \
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -33,6 +33,7 @@ public class TestAll {
|
||||
private static final Class[] CLASSES = {
|
||||
TestDefault.class,
|
||||
TestChannel.class,
|
||||
TestCleaner.class,
|
||||
TestConnect.class,
|
||||
TestConnectAgain.class,
|
||||
TestControl.class,
|
||||
|
||||
107
test/jdk/sun/security/smartcardio/TestCleaner.java
Normal file
107
test/jdk/sun/security/smartcardio/TestCleaner.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8380391
|
||||
* @summary basic test of CardImpl Cleaner
|
||||
* @modules java.base/java.lang.ref:open
|
||||
* @modules java.smartcardio/javax.smartcardio
|
||||
* @library /test/lib
|
||||
* @build jdk.test.whitebox.WhiteBox
|
||||
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||
* @run main/manual/othervm
|
||||
* -Xbootclasspath/a:.
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+WhiteBoxAPI
|
||||
* TestCleaner
|
||||
*/
|
||||
|
||||
// This test requires special hardware; a card must be present
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
import javax.smartcardio.Card;
|
||||
import javax.smartcardio.CardTerminal;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import jdk.test.whitebox.WhiteBox;
|
||||
import jtreg.SkippedException;
|
||||
|
||||
/**
|
||||
* Rudimentary test to confirm that the cleaning action does not prevent the
|
||||
* CardImpl from becoming unreachable and being collected/cleaned.
|
||||
*/
|
||||
public class TestCleaner extends Utils {
|
||||
static WhiteBox wb;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
CardTerminal terminal = null;
|
||||
try {
|
||||
terminal = getTerminal(args);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
if ("Error constructing TerminalFactory for PC/SC using SunPCSC".equals(e.getMessage())) {
|
||||
// Cause is expected to be a PCSCException
|
||||
if ("SCARD_E_NO_SERVICE".equals(e.getCause().getMessage())) {
|
||||
throw new SkippedException("Skipping the test: " +
|
||||
"Unable to construct TerminalFactory");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (terminal == null) {
|
||||
throw new SkippedException("Skipping the test: " +
|
||||
"no card terminals available");
|
||||
}
|
||||
|
||||
while (!terminal.isCardPresent()) {
|
||||
System.out.println("*** Insert card!");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
// Connect using any available protocol
|
||||
Card card = terminal.connect("*");
|
||||
if (card == null) {
|
||||
throw new SkippedException("Skipping the test: " +
|
||||
"no card available");
|
||||
}
|
||||
System.out.println("card is " + card);
|
||||
|
||||
// Ensure card object can become unreachable
|
||||
WeakHashMap<Card,Object> whm = new WeakHashMap<>();
|
||||
whm.put(card, new Object());
|
||||
|
||||
System.out.println("Allow card object to be collected");
|
||||
card = null;
|
||||
terminal = null;
|
||||
|
||||
wb = WhiteBox.getWhiteBox();
|
||||
wb.fullGC();
|
||||
wb.waitForReferenceProcessing();
|
||||
|
||||
if (whm.size() > 0) {
|
||||
throw new RuntimeException("*** TEST FAILED - Card could not be collected");
|
||||
}
|
||||
System.out.println("Card object collected.");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user