Códigos de baixa qualidade levam a comportamentos imprevisíveis. Da perspectiva do usuário, isso normalmente se manifesta como usabilidade ruim. Para um invasor, trata-se de uma oportunidade para atacar o sistema de formas imprevistas.
Camera
depois que este já foi liberado.Camera
depois que ele já foi liberado. Qualquer referência adicional ao objeto Camera
sem a reaquisição do recurso lançará uma exceção e poderá fazer com que o aplicativo trave se essa exceção não for detectada.startPreview()
será chamado no objeto Camera
anteriormente liberado.
public class ReuseCameraActivity extends Activity {
private Camera cam;
...
private class CameraButtonListener implements OnClickListener {
public void onClick(View v) {
if (toggle) {
cam.stopPreview();
cam.release();
}
else {
cam.startPreview();
}
toggle = !toggle;
}
}
...
}
start()
será chamado no recurso de mídia anteriormente liberado.
public class ReuseMediaPlayerActivity extends Activity {
private MediaPlayer mp;
...
private class PauseButtonListener implements OnClickListener {
public void onClick(View v) {
if (paused) {
mp.pause();
mp.release();
}
else {
mp.start();
}
paused = !paused;
}
}
...
}
flushUpdates()
para confirmar as alterações no disco. O método fecha corretamente o manipulador de banco de dados depois de gravar atualizações no banco de dados. No entanto, quando flushUpdates()
é chamado novamente, o objeto de banco de dados é referenciado novamente antes da reinicialização.
public class ReuseDBActivity extends Activity {
private myDBHelper dbHelper;
private SQLiteDatabase db;
@Override
public void onCreate(Bundle state) {
...
db = dbHelper.getWritableDatabase();
...
}
...
private void flushUpdates() {
db.insert(cached_data); // flush cached data
dbHelper.close();
}
...
}
...
<script src="http://applicationserver.application.com/lib/jquery/jquery-1.4.2.js" type="text/javascript"></script>
...
String
pode levar à perda de dados.String
, não fica claro o que acontecerá com os dados que estiverem fora do conjunto de caracteres aplicável. Isso pode provocar perda de dados ou uma diminuição no nível de segurança quando dados binários são necessários para assegurar que medidas de segurança adequadas sejam seguidas.
...
FileInputStream fis = new FileInputStream(myFile);
byte[] byteArr = byte[BUFSIZE];
...
int count = fis.read(byteArr);
...
String fileString = new String(byteArr);
String fileSHA256Hex = DigestUtils.sha256Hex(fileString);
// use fileSHA256Hex to validate file
...
BUFSIZE
, desde que as informações em myFile
sejam codificadas da mesma maneira que o conjunto de caracteres padrão. Porém, se uma codificação diferente estiver em uso, ou se o arquivo for binário, haverá perda de informações. Isso por sua vez fará com que o hash SHA resultante seja menos confiável e pode implicar que colisões podem ser provocadas com muito mais facilidade, especialmente se os dados fora do conjunto de caracteres padrão forem representados pelo mesmo valor, como um ponto de interrogação.notify()
for chamado.notify()
.notifyJob()
chama notify()
.
public synchronized notifyJob() {
flag = true;
notify();
}
...
public synchronized waitForSomething() {
while(!flag) {
try {
wait();
}
catch (InterruptedException e)
{
...
}
}
...
}
wait()
, mas é possível que notify()
notifique um thread diferente do pretendido.run()
de um thread em vez de chamar start()
.run()
de um objeto Thread
é um bug. O programador pretendia iniciar um novo thread de controle, mas acidentalmente chamou run()
no lugar de start()
e, portanto, o método run()
será executado no thread de controle do chamador.run()
em vez de start()
.
Thread thr = new Thread() {
public void run() {
...
}
};
thr.run();
stop()
de um thread, possivelmente deixando vazar recursos.stop()
de um objeto Thread
é um bug. O programador pretendia parar a execução de um thread, mas não sabia que esta não é uma maneira adequada de fazer isso. A função stop()
dentro de Thread
causa uma exceção ThreadDeath
em qualquer lugar dentro do objeto Thread
, provavelmente deixando objetos em um estado inconsistente e possivelmente deixando vazar recursos. Como essa API é inerentemente insegura, seu uso foi preterido há muito tempo.Thread.stop()
.
...
public static void main(String[] args){
...
Thread thr = new Thread() {
public void run() {
...
}
};
...
thr.start();
...
thr.stop();
...
}
clone()
, mas não implementa a interface Cloneable
.Cloneable
, pois ela implementa um método denominado clone()
. No entanto, a classe não implementa a interface Cloneable
, e o método clone()
não se comportará corretamente.clone()
para essa chamada resultará em uma CloneNotSupportedException.
public class Kibitzer {
public Object clone() throws CloneNotSupportedException {
...
}
}
ICloneable
especifica um contrato fraco para seu método Clone
e deve ser evitada.ICloneable
não garante uma clonagem profunda. As classes que a implementam podem não apresentar o comportamento esperado ao serem clonadas. Classes que implementam ICloneable
e executam apenas uma clonagem superficial (cópias apenas do objeto, o que inclui referências existentes a outros objetos) podem resultar em um comportamento inesperado. Como a clonagem profunda (cópias do objeto e de todos os objetos referenciados) é normalmente o comportamento assumida de um método de clone, o uso da interface ICloneable
é propenso a erros e deve ser evitado.clone()
na classe chama uma função que pode ser substituída.clone()
chama uma função substituível, ela pode fazer com que o clone seja deixado em um estado parcialmente inicializado ou se torne corrompido.clone()
a seguir chama um método que pode ser substituído.
...
class User implements Cloneable {
private String username;
private boolean valid;
public Object clone() throws CloneNotSupportedException {
final User clone = (User) super.clone();
clone.doSomething();
return clone;
}
public void doSomething(){
...
}
}
doSomething()
e a sua classe delimitadora não são final
, significa que a função pode ser substituída, o que pode deixar o objeto clone
clonado em um estado parcialmente inicializado, capaz de provocar erros, se não estiver trabalhando em torno da lógica de uma forma inesperada.equals()
pode resultar em um comportamento inesperado.equals()
da primitiva encaixotada deve ser chamado em vez dos operadores ==
e !=
. A Especificação Java declara o seguinte sobre conversões de encaixotamento: Boolean
ou Byte
), apenas uma faixa de valores será armazenada em cache ou memorizada. Para um subconjunto de valores, o uso de ==
ou !=
retornará o valor correto. Para todos os outros valores fora desse subconjunto, isso retornará o resultado da comparação dos endereços de objetos.
...
Integer mask0 = 100;
Integer mask1 = 100;
...
if (file0.readWriteAllPerms){
mask0 = 777;
}
if (file1.readWriteAllPerms){
mask1 = 777;
}
...
if (mask0 == mask1){
//assume file0 and file1 have same permissions
...
}
...
Example 1
usa primitivas encaixotadas Integer
para tentar comparar dois valores int
. Se mask0
e mask1
forem ambos iguais a 100
, mask0 == mask1
retornará true
. No entanto, quando mask0
e mask1
forem ambos iguais a 777
, mask0 == maske1
agora retornará false
, pois esses valores não estão dentro do intervalo de valores armazenados em cache para essas primitivas encaixotadas.NaN
é sempre um erro.NaN
, ela é sempre avaliada como false
, exceto para o operador !=
, que sempre é avaliado como true
, já que NaN
não está ordenado.NaN
.
...
if (result == Double.NaN){
//something went wrong
throw new RuntimeException("Something went wrong, NaN found");
}
...
result
não é NaN
, mas o uso do operador ==
com NaN
sempre resulta em um valor de false
, e, portanto, essa verificação nunca lançará a exceção.this
antes que o objeto seja totalmente inicializado, o que, por sua vez, pode provocar uma vulnerabilidade.
...
class User {
private String username;
private boolean valid;
public User(String username, String password){
this.username = username;
this.valid = validateUser(username, password);
}
public boolean validateUser(String username, String password){
//validate user is real and can authenticate
...
}
public final boolean isValid(){
return valid;
}
}
validateUser
e a classe não são final
, isso significa que elas podem ser substituídas e, dessa forma, inicializar uma variável para a subclasse que substitui essa função possibilitaria o desvio da funcionalidade validateUser
. Por exemplo:
...
class Attacker extends User{
public Attacker(String username, String password){
super(username, password);
}
public boolean validateUser(String username, String password){
return true;
}
}
...
class MainClass{
public static void main(String[] args){
User hacker = new Attacker("Evil", "Hacker");
if (hacker.isValid()){
System.out.println("Attack successful!");
}else{
System.out.println("Attack failed");
}
}
}
Example 1
imprime "Attack successful!", uma vez que a classe Attacker
substitui a função validateUser()
que é chamada a partir do construtor da superclasse User
, e o Java primeiro examinará a subclasse em busca de funções chamadas a partir desse construtor.inputReader
é confiável com base em seu nome de classe. Se um invasor puder fornecer uma implementação de inputReader
que executa comandos mal-intencionados, esse código não poderá diferenciar as versões bem-intencionadas das mal-intencionadas do objeto.
if (inputReader.GetType().FullName == "CompanyX.Transaction.Monetary")
{
processTransaction(inputReader);
}
inputReader
é confiável com base em seu nome de classe. Se um invasor puder fornecer uma implementação de inputReader
que executa comandos mal-intencionados, esse código não poderá diferenciar as versões bem-intencionadas das mal-intencionadas do objeto.
if (inputReader.getClass().getName().equals("com.example.TrustedClass")) {
input = inputReader.getInput();
...
}
inputReader
é confiável com base em seu nome de classe. Se um invasor puder fornecer uma implementação de inputReader
que executa comandos mal-intencionados, esse código não poderá diferenciar as versões bem-intencionadas das mal-intencionadas do objeto.
if (inputReader::class.qualifiedName == "com.example.TrustedClass") {
input = inputReader.getInput()
...
}
x = NULL
e x != NULL
serão sempre false.NULL
é indeterminado. Não será equivalente a nada, nem mesmo a um outro valor NULL
. Além disso, um valor null
nunca é equivalente a outro valor.Exemplo 2: Esta instrução será sempre false.
checkNull BOOLEAN := x = NULL;
checkNotNull BOOLEAN := x != NULL;
equals()
, e não com ==
ou !=
.==
ou !=
para comparar a igualdade de duas strings, o que compara a igualdade de dois objetos, e não seus valores. São boas as chances de que as duas referências nunca serão iguais.
if (args[0] == STRING_CONSTANT) {
logger.info("miracle");
}
==
e !=
apenas se comportarão conforme esperado quando forem usados para comparar strings contidas em objetos que são iguais. A maneira mais comum de isso acontecer é internalizando as strings, processo pelo qual elas são adicionadas a um pool de objetos mantidos pela classe String
. Após a internalização de uma string, todas as suas aplicações usarão o mesmo objeto, e os operadores de igualdade terão o comportamento esperado. Todos os literais de string e constantes com valores de string são internalizados automaticamente. Outras strings podem ser internalizadas manualmente chamando String.intern()
, o que retornará uma instância canônica da string atual, criando uma se necessário.